Compare commits

...

14 Commits

Author SHA1 Message Date
Hosted Weblate
2a8eacedb8
Translated using Weblate (Norwegian Bokmål)
Currently translated at 100.0% (72 of 72 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (596 of 596 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (48 of 48 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (124 of 124 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (72 of 72 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (596 of 596 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (88 of 88 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (124 of 124 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (596 of 596 strings)

Translated using Weblate (Norwegian Bokmål)

Currently translated at 100.0% (90 of 90 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: OverTheHillsAndFarAway <prosjektx@users.noreply.hosted.weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-filter/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/nb_NO/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/nb_NO/
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/components-filter
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
2025-11-01 14:19:48 +00:00
Hosted Weblate
21da0c979f
Translated using Weblate (Chinese (Simplified Han script))
Currently translated at 100.0% (88 of 88 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (596 of 596 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (124 of 124 strings)

Translated using Weblate (Chinese (Simplified Han script))

Currently translated at 100.0% (39 of 39 strings)

Co-authored-by: GuoQing Liu <842607283@qq.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/zh_Hans/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/zh_Hans/
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-settings
2025-11-01 14:19:47 +00:00
Hosted Weblate
d451cef1d5
Translated using Weblate (Slovak)
Currently translated at 85.9% (512 of 596 strings)

Translated using Weblate (Slovak)

Currently translated at 99.1% (123 of 124 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Slovak)

Currently translated at 98.6% (494 of 501 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (88 of 88 strings)

Translated using Weblate (Slovak)

Currently translated at 74.1% (442 of 596 strings)

Translated using Weblate (Slovak)

Currently translated at 98.3% (122 of 124 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (Slovak)

Currently translated at 98.1% (53 of 54 strings)

Translated using Weblate (Slovak)

Currently translated at 100.0% (206 of 206 strings)

Translated using Weblate (Slovak)

Currently translated at 88.0% (441 of 501 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Jakub K <klacanjakub0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/audio/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/sk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/sk/
Translation: Frigate NVR/audio
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-settings
2025-11-01 14:19:46 +00:00
Hosted Weblate
50f4f686a6
Translated using Weblate (French)
Currently translated at 100.0% (89 of 89 strings)

Translated using Weblate (French)

Currently translated at 100.0% (88 of 88 strings)

Translated using Weblate (French)

Currently translated at 100.0% (596 of 596 strings)

Translated using Weblate (French)

Currently translated at 100.0% (124 of 124 strings)

Translated using Weblate (French)

Currently translated at 100.0% (39 of 39 strings)

Co-authored-by: Apocoloquintose <bertrand.moreux@gmail.com>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/fr/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/fr/
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-settings
2025-11-01 14:19:45 +00:00
Hosted Weblate
5e23230df5
Translated using Weblate (Dutch)
Currently translated at 100.0% (89 of 89 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (88 of 88 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (596 of 596 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (124 of 124 strings)

Translated using Weblate (Dutch)

Currently translated at 100.0% (39 of 39 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Marijn <168113859+Marijn0@users.noreply.github.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/nl/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/nl/
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-settings
2025-11-01 14:19:44 +00:00
Hosted Weblate
15522b916e
Translated using Weblate (Italian)
Currently translated at 100.0% (89 of 89 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (88 of 88 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (596 of 596 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (124 of 124 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (206 of 206 strings)

Translated using Weblate (Italian)

Currently translated at 28.4% (25 of 88 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (Italian)

Currently translated at 100.0% (51 of 51 strings)

Translated using Weblate (Italian)

Currently translated at 75.0% (93 of 124 strings)

Translated using Weblate (Italian)

Currently translated at 98.1% (53 of 54 strings)

Co-authored-by: Gringo <ita.translations@tiscali.it>
Co-authored-by: Hosted Weblate <hosted@weblate.org>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/it/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/it/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
2025-11-01 14:19:42 +00:00
Hosted Weblate
516d84cc31
Translated using Weblate (Ukrainian)
Currently translated at 100.0% (89 of 89 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (88 of 88 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (596 of 596 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (51 of 51 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (124 of 124 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Ukrainian)

Currently translated at 100.0% (206 of 206 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Максим Горпиніч <gorpinicmaksim0@gmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/uk/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/uk/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-settings
2025-11-01 14:19:41 +00:00
Hosted Weblate
114def0617
Translated using Weblate (Romanian)
Currently translated at 100.0% (88 of 88 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (51 of 51 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (124 of 124 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (39 of 39 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (593 of 593 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (48 of 48 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (90 of 90 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (51 of 51 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (13 of 13 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (124 of 124 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (34 of 34 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (54 of 54 strings)

Translated using Weblate (Romanian)

Currently translated at 100.0% (206 of 206 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: lukasig <lukasig@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/common/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/components-dialog/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-classificationmodel/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-explore/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-exports/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-facelibrary/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-live/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-search/ro/
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-settings/ro/
Translation: Frigate NVR/common
Translation: Frigate NVR/components-dialog
Translation: Frigate NVR/views-classificationmodel
Translation: Frigate NVR/views-events
Translation: Frigate NVR/views-explore
Translation: Frigate NVR/views-exports
Translation: Frigate NVR/views-facelibrary
Translation: Frigate NVR/views-live
Translation: Frigate NVR/views-search
Translation: Frigate NVR/views-settings
2025-11-01 14:19:40 +00:00
Hosted Weblate
71f0472ff5
Translated using Weblate (Portuguese (Brazil))
Currently translated at 100.0% (34 of 34 strings)

Co-authored-by: Hosted Weblate <hosted@weblate.org>
Co-authored-by: Marcelo Popper Costa <marcelo_popper@hotmail.com>
Translate-URL: https://hosted.weblate.org/projects/frigate-nvr/views-events/pt_BR/
Translation: Frigate NVR/views-events
2025-11-01 14:19:39 +00:00
Josh Hawkins
36fb27ef56
Refactor Tracked Object Details dialog (#20748)
* detail stream settings

* remove old review detail dialog

* change layout

* use detail stream in tracking details

* reusable tabs component

* pass in tabs for desktop

* fix object selection and time updating

* i18n

* aspect fixes

* include tolerance for displaying of path and zone

some browsers (firefox and probably brave) intentionally reduce precision of seeking with currentTime for privacy reasons

* detail stream seeking fixes

* tracking details seeking fixes

* layout tweaks

* add download button back for now

* remove

* remove

* snapshot is now default tab
2025-11-01 09:19:30 -05:00
Nicolas Mowen
9937a7cc3d
Add ability to delete classification models (#20747)
* fix typo

* Add ability to delete classification models
2025-11-01 09:11:24 -05:00
Nicolas Mowen
7aac6b4f21
Don't remove tensorflow on trt (#20743) 2025-10-31 16:17:41 -05:00
Nicolas Mowen
338b681ed0
Various Tweaks (#20742)
* Pull context size from openai models

* Adjust wording based on type of model

* Instruct to not use parenthesis

* Simplify genai config

* Don't use GPU for training
2025-10-31 12:40:31 -06:00
Nicolas Mowen
685f2c5030
Mark delivery objects with marker so LLM knows what is happening (#20736) 2025-10-31 07:14:34 -05:00
77 changed files with 2200 additions and 1561 deletions

View File

@ -21,7 +21,7 @@ FROM deps AS frigate-tensorrt
ARG PIP_BREAK_SYSTEM_PACKAGES
RUN --mount=type=bind,from=trt-wheels,source=/trt-wheels,target=/deps/trt-wheels \
pip3 uninstall -y onnxruntime tensorflow-cpu \
pip3 uninstall -y onnxruntime \
&& pip3 install -U /deps/trt-wheels/*.whl
COPY --from=rootfs / /

View File

@ -13,7 +13,6 @@ nvidia_cusolver_cu12==11.6.3.*; platform_machine == 'x86_64'
nvidia_cusparse_cu12==12.5.1.*; platform_machine == 'x86_64'
nvidia_nccl_cu12==2.23.4; platform_machine == 'x86_64'
nvidia_nvjitlink_cu12==12.5.82; platform_machine == 'x86_64'
tensorflow==2.19.*; platform_machine == 'x86_64'
onnx==1.16.*; platform_machine == 'x86_64'
onnxruntime-gpu==1.22.*; platform_machine == 'x86_64'
protobuf==3.20.3; platform_machine == 'x86_64'

View File

@ -10,7 +10,6 @@ Object classification allows you to train a custom MobileNetV2 classification mo
Object classification models are lightweight and run very fast on CPU. Inference should be usable on virtually any machine that can run Frigate.
Training the model does briefly use a high amount of system resources for about 13 minutes per training run. On lower-power devices, training may take longer.
When running the `-tensorrt` image, Nvidia GPUs will automatically be used to accelerate training.
## Classes

View File

@ -10,7 +10,6 @@ State classification allows you to train a custom MobileNetV2 classification mod
State classification models are lightweight and run very fast on CPU. Inference should be usable on virtually any machine that can run Frigate.
Training the model does briefly use a high amount of system resources for about 13 minutes per training run. On lower-power devices, training may take longer.
When running the `-tensorrt` image, Nvidia GPUs will automatically be used to accelerate training.
## Classes

View File

@ -804,3 +804,42 @@ async def generate_object_examples(request: Request, body: GenerateObjectExample
content={"success": True, "message": "Example generation completed"},
status_code=200,
)
@router.delete(
"/classification/{name}",
response_model=GenericResponse,
dependencies=[Depends(require_role(["admin"]))],
summary="Delete a classification model",
description="""Deletes a specific classification model and all its associated data.
The name must exist in the classification models. Returns a success message or an error if the name is invalid.""",
)
def delete_classification_model(request: Request, name: str):
config: FrigateConfig = request.app.frigate_config
if name not in config.classification.custom:
return JSONResponse(
content=(
{
"success": False,
"message": f"{name} is not a known classification model.",
}
),
status_code=404,
)
# Delete the classification model's data directory
model_dir = os.path.join(CLIPS_DIR, sanitize_filename(name))
if os.path.exists(model_dir):
shutil.rmtree(model_dir)
return JSONResponse(
content=(
{
"success": True,
"message": f"Successfully deleted classification model {name}.",
}
),
status_code=200,
)

View File

@ -186,6 +186,7 @@ class ReviewDescriptionProcessor(PostProcessorApi):
thumbs,
camera_config.review.genai,
list(self.config.model.merged_labelmap.values()),
self.config.model.all_attributes,
),
).start()
@ -414,6 +415,7 @@ def run_analysis(
thumbs: list[bytes],
genai_config: GenAIReviewConfig,
labelmap_objects: list[str],
attribute_labels: list[str],
) -> None:
start = datetime.datetime.now().timestamp()
analytics_data = {
@ -441,7 +443,11 @@ def run_analysis(
continue
elif label in labelmap_objects:
object_type = label.replace("_", " ").title()
unified_objects.append(object_type)
if label in attribute_labels:
unified_objects.append(f"{object_type} (delivery/service)")
else:
unified_objects.append(object_type)
analytics_data["unified_objects"] = unified_objects

View File

@ -114,7 +114,7 @@ Your response MUST be a flat JSON object with:
## Objects in Scene
Each line represents a detection state, not necessarily unique individuals. Objects with names in parentheses (e.g., "Name (person)") are verified identities. Objects without names (e.g., "Person") are detected but not identified.
Each line represents a detection state, not necessarily unique individuals. Parentheses indicate object type or category, use only the name/label in your response, not the parentheses.
**CRITICAL: When you see both recognized and unrecognized entries of the same type (e.g., "Joe (person)" and "Person"), visually count how many distinct people/objects you actually see based on appearance and clothing. If you observe only ONE person throughout the sequence, use ONLY the recognized name (e.g., "Joe"). The same person may be recognized in some frames but not others. Only describe both if you visually see MULTIPLE distinct people with clearly different appearances.**

View File

@ -18,6 +18,7 @@ class OpenAIClient(GenAIClient):
"""Generative AI client for Frigate using OpenAI."""
provider: OpenAI
context_size: Optional[int] = None
def _init_provider(self):
"""Initialize the client."""
@ -69,5 +70,33 @@ class OpenAIClient(GenAIClient):
def get_context_size(self) -> int:
"""Get the context window size for OpenAI."""
# OpenAI GPT-4 Vision models have 128K token context window
return 128000
if self.context_size is not None:
return self.context_size
try:
models = self.provider.models.list()
for model in models.data:
if model.id == self.genai_config.model:
if hasattr(model, "max_model_len") and model.max_model_len:
self.context_size = model.max_model_len
logger.debug(
f"Retrieved context size {self.context_size} for model {self.genai_config.model}"
)
return self.context_size
except Exception as e:
logger.debug(
f"Failed to fetch model context size from API: {e}, using default"
)
# Default to 128K for ChatGPT models, 8K for others
model_name = self.genai_config.model.lower()
if "gpt" in model_name:
self.context_size = 128000
else:
self.context_size = 8192
logger.debug(
f"Using default context size {self.context_size} for model {self.genai_config.model}"
)
return self.context_size

View File

@ -384,10 +384,10 @@ def migrate_017_0(config: dict[str, dict[str, Any]]) -> dict[str, dict[str, Any]
new_object_config["genai"] = {}
for key in global_genai.keys():
if key not in ["enabled", "model", "provider", "base_url", "api_key"]:
new_object_config["genai"][key] = global_genai[key]
else:
if key in ["model", "provider", "base_url", "api_key"]:
new_genai_config[key] = global_genai[key]
else:
new_object_config["genai"][key] = global_genai[key]
config["genai"] = new_genai_config

View File

@ -5,12 +5,15 @@
"renameCategory": "Rename Class",
"deleteCategory": "Delete Class",
"deleteImages": "Delete Images",
"trainModel": "Train Model"
"trainModel": "Train Model",
"addClassification": "Add Classification",
"deleteModels": "Delete Models"
},
"toast": {
"success": {
"deletedCategory": "Deleted Class",
"deletedImage": "Deleted Images",
"deletedModel": "Successfully deleted {{count}} model(s)",
"categorizedImage": "Successfully Classified Image",
"trainedModel": "Successfully trained model.",
"trainingModel": "Successfully started model training."
@ -18,6 +21,7 @@
"error": {
"deleteImageFailed": "Failed to delete: {{errorMessage}}",
"deleteCategoryFailed": "Failed to delete class: {{errorMessage}}",
"deleteModelFailed": "Failed to delete model: {{errorMessage}}",
"categorizeFailed": "Failed to categorize image: {{errorMessage}}",
"trainingFailed": "Failed to start model training: {{errorMessage}}"
}
@ -26,6 +30,11 @@
"title": "Delete Class",
"desc": "Are you sure you want to delete the class {{name}}? This will permanently delete all associated images and require re-training the model."
},
"deleteModel": {
"title": "Delete Classification Model",
"single": "Are you sure you want to delete {{name}}? This will permanently delete all associated data including images and training data. This action cannot be undone.",
"desc": "Are you sure you want to delete {{count}} model(s)? This will permanently delete all associated data including images and training data. This action cannot be undone."
},
"deleteDatasetImages": {
"title": "Delete Dataset Images",
"desc": "Are you sure you want to delete {{count}} images from {{dataset}}? This action cannot be undone and will require re-training the model."
@ -52,6 +61,10 @@
},
"categorizeImageAs": "Classify Image As:",
"categorizeImage": "Classify Image",
"menu": {
"objects": "Objects",
"states": "States"
},
"noModels": {
"object": {
"title": "No Object Classification Models",
@ -86,6 +99,7 @@
"classificationSubLabel": "Sub Label",
"classificationAttribute": "Attribute",
"classes": "Classes",
"states": "States",
"classesTip": "Learn about classes",
"classesStateDesc": "Define the different states your camera area can be in. For example: 'open' and 'closed' for a garage door.",
"classesObjectDesc": "Define the different categories to classify detected objects into. For example: 'delivery_person', 'resident', 'stranger' for person classification.",

View File

@ -33,6 +33,7 @@
"type": {
"details": "details",
"snapshot": "snapshot",
"thumbnail": "thumbnail",
"video": "video",
"object_lifecycle": "object lifecycle"
},
@ -41,7 +42,7 @@
"noImageFound": "No image found for this timestamp.",
"createObjectMask": "Create Object Mask",
"adjustAnnotationSettings": "Adjust annotation settings",
"scrollViewTips": "Scroll to view the significant moments of this object's lifecycle.",
"scrollViewTips": "Click to view the significant moments of this object's lifecycle.",
"autoTrackingTips": "Bounding box positions will be inaccurate for autotracking cameras.",
"count": "{{first}} of {{second}}",
"trackedPoint": "Tracked Point",

View File

@ -43,7 +43,8 @@
},
"train": {
"title": "Classifications récentes",
"aria": "Sélectionner des classifications récentes"
"aria": "Sélectionner des classifications récentes",
"titleShort": "Récent"
},
"categories": "Classes",
"createCategory": {
@ -98,7 +99,8 @@
"stateRequiresTwoClasses": "Les modèles d'état nécessitent au moins deux classes.",
"objectLabelRequired": "Veuillez sélectionner une étiquette d'objet.",
"objectTypeRequired": "Veuillez sélectionner un type de classification."
}
},
"states": "États"
},
"step2": {
"description": "Sélectionnez les caméras et définissez la zone à surveiller pour chaque caméra. Le modèle classifiera l'état de ces zones.",

View File

@ -44,10 +44,17 @@
"trackedObject_one": "objet",
"trackedObject_other": "objets",
"noObjectDetailData": "Aucun détail d'objet disponible",
"label": "Détail"
"label": "Détail",
"settings": "Paramètres de la vue Détail",
"alwaysExpandActive": {
"title": "Toujours développer l'élément actif",
"desc": "Toujours développer les détails de l'objet de l'événement actif si disponibles"
}
},
"objectTrack": {
"trackedPoint": "Point suivi",
"clickToSeek": "Cliquez pour atteindre ce moment."
}
},
"zoomIn": "Zoom avant",
"zoomOut": "Zoom arrière"
}

View File

@ -264,7 +264,7 @@
},
"annotationSettings": {
"offset": {
"desc": "Ces données proviennent du flux de détection de votre caméra mais sont superposées aux images du flux d'enregistrement. Il est peu probable que les deux flux soient parfaitement synchronisés. Par conséquent, le cadre de détection et l'enregistrement ne seront pas parfaitement alignés. Cependant, le champ <code>annotation_offset</code> peut être utilisé pour corriger cela.",
"desc": "Ces données proviennent du flux de détection de votre caméra, mais elles sont superposées aux images du flux d'enregistrement. Il est peu probable que les deux flux soient parfaitement synchronisés. Par conséquent, le cadre de délimitation et la vidéo ne s'aligneront pas parfaitement. Vous pouvez utiliser ce paramètre pour décaler les annotations vers l'avant ou vers l'arrière dans le temps afin de mieux les aligner avec la vidéo enregistrée.",
"millisecondsToOffset": "Millisecondes de décalage pour les annotations de détection. <em>Par défaut : 0</em>",
"tips": "ASTUCE : Imaginez une séquence d'événement avec une personne marchant de gauche à droite. Si le cadre de détection sur la chronologie de l'événement est constamment à gauche de la personne, la valeur doit être diminuée. De même, si une personne marche de gauche à droite et que le cadre de détection est constamment devant la personne, la valeur doit être augmentée.",
"toast": {

View File

@ -51,6 +51,10 @@
"playAlertVideos": {
"label": "Lire les vidéos d'alerte",
"desc": "Par défaut, les alertes récentes du tableau de bord en direct sont diffusées sous forme de petites vidéos en boucle. Désactivez cette option pour afficher uniquement une image statique des alertes récentes sur cet appareil/navigateur."
},
"displayCameraNames": {
"label": "Toujours afficher les noms des caméras",
"desc": "Toujours afficher les noms des caméras dans une puce sur le tableau de bord de la vue en direct multi-caméras"
}
},
"storedLayouts": {
@ -767,7 +771,9 @@
},
"actions": {
"alert": "Marquer comme alerte",
"notification": "Envoyer une notification"
"notification": "Envoyer une notification",
"sub_label": "Ajouter une sous-étiquette",
"attribute": "Ajouter un attribut"
},
"dialog": {
"createTrigger": {
@ -822,7 +828,7 @@
},
"actions": {
"title": "Actions",
"desc": "Par défaut, Frigate publie un message MQTT à chaque déclenchement. Sélectionnez une action complémentaire à exécuter lors de ce déclenchement.",
"desc": "Par défaut, Frigate envoie un message MQTT pour tous les déclencheurs. Les sous-étiquettes ajoutent le nom du déclencheur à l'étiquette de l'objet. Les attributs sont des métadonnées recherchables stockées séparément dans les métadonnées de l'objet suivi.",
"error": {
"min": "Au moins une action doit être sélectionnée."
}

View File

@ -145,7 +145,10 @@
}
},
"label": {
"back": "Vai indietro"
"back": "Vai indietro",
"hide": "Nascondi {{item}}",
"show": "Mostra {{item}}",
"ID": "ID"
},
"menu": {
"configuration": "Configurazione",
@ -265,7 +268,7 @@
"title": "Ruolo",
"admin": "Amministratore",
"viewer": "Spettatore",
"desc": "Gli Amministratori hanno accesso completo a tutte le funzionalità dell'interfaccia di Frigate. Gli Spettatori sono limitati alla sola visualizzazione delle telecamere, rivedono gli oggetti e le registrazioni storiche nell'interfaccia utente."
"desc": "Gli amministratori hanno accesso completo a tutte le funzionalità dell'interfaccia utente di Frigate. Gli spettatori possono visualizzare solo le telecamere, gli elementi di revisione e i filmati storici nell'interfaccia utente."
},
"accessDenied": {
"desc": "Non hai i permessi per visualizzare questa pagina.",
@ -291,5 +294,13 @@
"readTheDocumentation": "Leggi la documentazione",
"information": {
"pixels": "{{area}}px"
},
"list": {
"two": "{{0}} e {{1}}",
"many": "{{items}}, e {{last}}"
},
"field": {
"optional": "Opzionale",
"internalID": "L'ID interno che Frigate utilizza nella configurazione e nel database"
}
}

View File

@ -61,7 +61,7 @@
"export": "Esporta",
"selectOrExport": "Seleziona o esporta",
"toast": {
"success": "Esportazione avviata correttamente. Visualizza il file nella cartella /exports.",
"success": "Esportazione avviata correttamente. Visualizza il file nella pagina delle esportazioni.",
"error": {
"failed": "Impossibile avviare l'esportazione: {{error}}",
"endTimeMustAfterStartTime": "L'ora di fine deve essere successiva all'ora di inizio",
@ -129,6 +129,7 @@
"search": {
"placeholder": "Cerca per etichetta o sottoetichetta..."
},
"noImages": "Nessuna miniatura trovata per questa fotocamera"
"noImages": "Nessuna miniatura trovata per questa fotocamera",
"unknownLabel": "Immagine di attivazione salvata"
}
}

View File

@ -1 +1,137 @@
{}
{
"documentTitle": "Modelli di classificazione",
"button": {
"deleteClassificationAttempts": "Elimina immagini di classificazione",
"renameCategory": "Rinomina classe",
"deleteCategory": "Elimina classe",
"deleteImages": "Elimina immagini",
"trainModel": "Modello di addestramento"
},
"toast": {
"success": {
"deletedCategory": "Classe eliminata",
"deletedImage": "Immagini eliminate",
"categorizedImage": "Immagine classificata con successo",
"trainedModel": "Modello addestrato con successo.",
"trainingModel": "Avviato con successo l'addestramento del modello."
},
"error": {
"deleteImageFailed": "Impossibile eliminare: {{errorMessage}}",
"deleteCategoryFailed": "Impossibile eliminare la classe: {{errorMessage}}",
"categorizeFailed": "Impossibile categorizzare l'immagine: {{errorMessage}}",
"trainingFailed": "Impossibile avviare l'addestramento del modello: {{errorMessage}}"
}
},
"deleteCategory": {
"title": "Elimina classe",
"desc": "Vuoi davvero eliminare la classe {{name}}? Questa operazione eliminerà definitivamente tutte le immagini associate e richiederà un nuovo addestramento del modello."
},
"deleteDatasetImages": {
"title": "Elimina immagini della base dati",
"desc": "Vuoi davvero eliminare {{count}} immagini da {{dataset}}? Questa azione non può essere annullata e richiederà un nuovo addestramento del modello."
},
"deleteTrainImages": {
"title": "Elimina le immagini di addestramento",
"desc": "Vuoi davvero eliminare {{count}} immagini? Questa azione non può essere annullata."
},
"renameCategory": {
"title": "Rinomina classe",
"desc": "Inserisci un nuovo nome per {{name}}. Sarà necessario riaddestrare il modello affinché la modifica del nome abbia effetto."
},
"description": {
"invalidName": "Nome non valido. I nomi possono contenere solo lettere, numeri, spazi, apostrofi, caratteri di sottolineatura e trattini."
},
"train": {
"title": "Classificazioni recenti",
"titleShort": "Recente",
"aria": "Seleziona classificazioni recenti"
},
"categories": "Classi",
"createCategory": {
"new": "Crea nuova classe"
},
"categorizeImageAs": "Classifica immagine come:",
"categorizeImage": "Classifica immagine",
"noModels": {
"object": {
"title": "Nessun modello di classificazione degli oggetti",
"description": "Crea un modello personalizzato per classificare gli oggetti rilevati.",
"buttonText": "Crea modello oggetto"
},
"state": {
"title": "Nessun modello di classificazione dello stato",
"description": "Crea un modello personalizzato per monitorare e classificare i cambiamenti di stato in aree specifiche della telecamera.",
"buttonText": "Crea modello di stato"
}
},
"wizard": {
"title": "Crea nuova classificazione",
"steps": {
"nameAndDefine": "Nome e definizione",
"stateArea": "Area di stato",
"chooseExamples": "Scegli esempi"
},
"step1": {
"description": "I modelli di stato monitorano le aree fisse delle telecamere per rilevare eventuali cambiamenti (ad esempio, porta aperta/chiusa). I modelli di oggetti aggiungono classificazioni agli oggetti rilevati (ad esempio, animali noti, addetti alle consegne, ecc.).",
"name": "Nome",
"namePlaceholder": "Inserisci il nome del modello...",
"type": "Tipo",
"typeState": "Stato",
"typeObject": "Oggetto",
"objectLabel": "Etichetta oggetto",
"objectLabelPlaceholder": "Seleziona il tipo di oggetto...",
"classificationType": "Tipo di classificazione",
"classificationTypeTip": "Scopri i tipi di classificazione",
"classificationTypeDesc": "Le sottoetichette aggiungono testo aggiuntivo all'etichetta dell'oggetto (ad esempio, \"Persona: UPS\"). Gli attributi sono metadati ricercabili, archiviati separatamente nei metadati dell'oggetto.",
"classificationSubLabel": "Etichetta secondaria",
"classificationAttribute": "Attributo",
"classes": "Classi",
"classesTip": "Scopri di più sulle classi",
"classesStateDesc": "Definisci i diversi stati in cui può trovarsi l'area della tua telecamera. Ad esempio: \"aperto\" e \"chiuso\" per una porta del garage.",
"classesObjectDesc": "Definisci le diverse categorie in cui classificare gli oggetti rilevati. Ad esempio: \"corriere\", \"residente\", \"straniero\" per la classificazione delle persone.",
"classPlaceholder": "Inserisci il nome della classe...",
"errors": {
"nameRequired": "Il nome del modello è obbligatorio",
"nameLength": "Il nome del modello deve contenere al massimo 64 caratteri",
"nameOnlyNumbers": "Il nome del modello non può contenere solo numeri",
"classRequired": "È richiesta almeno 1 classe",
"classesUnique": "I nomi delle classi devono essere univoci",
"stateRequiresTwoClasses": "I modelli di stato richiedono almeno 2 classi",
"objectLabelRequired": "Seleziona un'etichetta per l'oggetto",
"objectTypeRequired": "Seleziona un tipo di classificazione"
},
"states": "Stati"
},
"step2": {
"description": "Seleziona le telecamere e definisci l'area da monitorare per ciascuna telecamera. Il modello classificherà lo stato di queste aree.",
"cameras": "Telecamere",
"selectCamera": "Seleziona telecamera",
"noCameras": "Fai clic su + per aggiungere telecamere",
"selectCameraPrompt": "Selezionare una telecamera dall'elenco per definire la sua area di monitoraggio"
},
"step3": {
"selectImagesPrompt": "Seleziona tutte le immagini con: {{className}}",
"selectImagesDescription": "Clicca sulle immagini per selezionarle. Clicca su Continua quando hai finito con questa classe.",
"generating": {
"title": "Generazione di immagini campione",
"description": "Frigate sta estraendo immagini rappresentative dalle registrazioni. L'operazione potrebbe richiedere qualche istante..."
},
"training": {
"title": "Modello di addestramento",
"description": "Il tuo modello è in fase di addestramento in sottofondo. Chiudi questa finestra di dialogo e il tuo modello inizierà a funzionare non appena l'addestramento sarà completato."
},
"retryGenerate": "Riprova generazione",
"noImages": "Nessuna immagine campione generata",
"classifying": "Classificazione e addestramento...",
"trainingStarted": "Addestramento iniziato con successo",
"errors": {
"noCameras": "Nessuna telecamera configurata",
"noObjectLabel": "Nessuna etichetta oggetto selezionata",
"generateFailed": "Impossibile generare esempi: {{error}}",
"generationFailed": "Generazione fallita. Per favore riprova.",
"classifyFailed": "Impossibile classificare le immagini: {{error}}"
},
"generateSuccess": "Immagini campione generate correttamente"
}
}
}

View File

@ -44,10 +44,17 @@
"trackedObject_one": "oggetto",
"trackedObject_other": "oggetti",
"noObjectDetailData": "Non sono disponibili dati dettagliati sull'oggetto.",
"label": "Dettaglio"
"label": "Dettaglio",
"settings": "Impostazioni di visualizzazione dettagliata",
"alwaysExpandActive": {
"title": "Espandi sempre attivo",
"desc": "Espandere sempre i dettagli dell'oggetto dell'elemento di revisione attivo quando disponibili."
}
},
"objectTrack": {
"trackedPoint": "Punto tracciato",
"clickToSeek": "Premi per cercare in questo momento"
}
},
"zoomIn": "Ingrandisci",
"zoomOut": "Rimpicciolisci"
}

View File

@ -201,11 +201,15 @@
},
"hideObjectDetails": {
"label": "Nascondi il percorso dell'oggetto"
},
"viewTrackingDetails": {
"label": "Visualizza i dettagli di tracciamento",
"aria": "Mostra i dettagli di tracciamento"
}
},
"dialog": {
"confirmDelete": {
"desc": "L'eliminazione di questo oggetto tracciato rimuove l'istantanea, eventuali incorporamenti salvati e tutte le voci associate al ciclo di vita dell'oggetto. Il filmato registrato di questo oggetto tracciato nella vista Storico <em>NON</em> verrà eliminato.<br /><br />Vuoi davvero procedere?",
"desc": "L'eliminazione di questo oggetto tracciato rimuove l'istantanea, eventuali incorporamenti salvati e tutte le voci associate ai dettagli di tracciamento. Il filmato registrato di questo oggetto tracciato nella vista Storico <em>NON</em> verrà eliminato.<br /><br />Vuoi davvero procedere?",
"title": "Conferma eliminazione"
}
},
@ -230,5 +234,53 @@
},
"concerns": {
"label": "Preoccupazioni"
},
"trackingDetails": {
"title": "Dettagli di tracciamento",
"noImageFound": "Nessuna immagine trovata per questo orario.",
"createObjectMask": "Crea maschera oggetto",
"adjustAnnotationSettings": "Regola le impostazioni di annotazione",
"scrollViewTips": "Scorri per visualizzare i momenti più significativi del ciclo di vita di questo oggetto.",
"autoTrackingTips": "Le posizioni dei riquadri di delimitazione saranno imprecise per le telecamere con tracciamento automatico.",
"count": "{{first}} di {{second}}",
"trackedPoint": "Punto tracciato",
"lifecycleItemDesc": {
"visible": "{{label}} rilevato",
"entered_zone": "{{label}} è entrato in {{zones}}",
"active": "{{label}} è diventato attivo",
"stationary": "{{label}} è diventato stazionario",
"attribute": {
"faceOrLicense_plate": "{{attribute}} rilevato per {{label}}",
"other": "{{label}} riconosciuto come {{attribute}}"
},
"gone": "{{label}} lasciato",
"heard": "{{label}} sentito",
"external": "{{label}} rilevato",
"header": {
"zones": "Zone",
"ratio": "Rapporto",
"area": "Area"
}
},
"annotationSettings": {
"title": "Impostazioni di annotazione",
"showAllZones": {
"title": "Mostra tutte le zone",
"desc": "Mostra sempre le zone nei fotogrammi in cui gli oggetti sono entrati in una zona."
},
"offset": {
"label": "Differenza annotazione",
"desc": "Questi dati provengono dal flusso di rilevamento della telecamera, ma vengono sovrapposti alle immagini del flusso di registrazione. È improbabile che i due flussi siano perfettamente sincronizzati. Di conseguenza, il riquadro di delimitazione e il filmato non saranno perfettamente allineati. È possibile utilizzare questa impostazione per spostare le annotazioni in avanti o indietro nel tempo per allinearle meglio al filmato registrato.",
"millisecondsToOffset": "Millisecondi per compensare il rilevamento delle annotazioni. <em>Predefinito: 0</em>",
"tips": "SUGGERIMENTO: Immagina un video evento con una persona che cammina da sinistra a destra. Se il riquadro di delimitazione della cronologia dell'evento si trova costantemente a sinistra della persona, il valore dovrebbe essere diminuito. Allo stesso modo, se una persona cammina da sinistra a destra e il riquadro di delimitazione si trova costantemente davanti alla persona, il valore dovrebbe essere aumentato.",
"toast": {
"success": "La differenza dell'annotazione per {{camera}} è stato salvato nel file di configurazione. Riavvia Frigate per applicare le modifiche."
}
}
},
"carousel": {
"previous": "Diapositiva precedente",
"next": "Diapositiva successiva"
}
}
}

View File

@ -13,5 +13,11 @@
"error": {
"renameExportFailed": "Impossibile rinominare l'esportazione: {{errorMessage}}"
}
},
"tooltip": {
"shareExport": "Condividi esportazione",
"downloadVideo": "Scarica video",
"editName": "Modifica nome",
"deleteExport": "Elimina esportazione"
}
}

View File

@ -1,7 +1,7 @@
{
"selectItem": "Seleziona {{item}}",
"description": {
"addFace": "Procedura per aggiungere una nuova raccolta alla Libreria dei Volti.",
"addFace": "Aggiungi una nuova raccolta alla Libreria dei Volti caricando la tua prima immagine.",
"placeholder": "Inserisci un nome per questa raccolta",
"invalidName": "Nome non valido. I nomi possono contenere solo lettere, numeri, spazi, apostrofi, caratteri di sottolineatura e trattini."
},

View File

@ -494,7 +494,11 @@
"label": "Riproduci video di avvisi",
"desc": "Per impostazione predefinita, gli avvisi recenti nella schermata dal vivo vengono riprodotti come brevi video in ciclo. Disattiva questa opzione per visualizzare solo un'immagine statica degli avvisi recenti su questo dispositivo/browser."
},
"title": "Schermata dal vivo"
"title": "Schermata dal vivo",
"displayCameraNames": {
"label": "Mostra sempre i nomi delle telecamere",
"desc": "Mostra sempre i nomi delle telecamere in una scheda nel cruscotto della visualizzazione dal vivo multi telecamera."
}
},
"title": "Impostazioni generali",
"storedLayouts": {
@ -745,7 +749,7 @@
"triggers": {
"documentTitle": "Inneschi",
"management": {
"title": "Gestione inneschi",
"title": "Inneschi",
"desc": "Gestisci gli inneschi per {{camera}}. Utilizza il tipo miniatura per attivare miniature simili all'oggetto tracciato selezionato e il tipo descrizione per attivare descrizioni simili al testo specificato."
},
"addTrigger": "Aggiungi innesco",
@ -766,7 +770,9 @@
},
"actions": {
"alert": "Contrassegna come avviso",
"notification": "Invia notifica"
"notification": "Invia notifica",
"sub_label": "Aggiungi sottoetichetta",
"attribute": "Aggiungi attributo"
},
"dialog": {
"createTrigger": {
@ -784,25 +790,28 @@
"form": {
"name": {
"title": "Nome",
"placeholder": "Inserisci il nome dell'innesco",
"placeholder": "Assegna un nome a questo innesco",
"error": {
"minLength": "Il nome deve essere lungo almeno 2 caratteri.",
"invalidCharacters": "Il nome può contenere solo lettere, numeri, caratteri di sottolineatura e trattini.",
"minLength": "Il campo deve contenere almeno 2 caratteri.",
"invalidCharacters": "Il campo può contenere solo lettere, numeri, caratteri di sottolineatura e trattini.",
"alreadyExists": "Per questa telecamera esiste già un innesco con questo nome."
}
},
"description": "Inserisci un nome o una descrizione univoca per identificare questo innesco"
},
"enabled": {
"description": "Abilita o disabilita questo innesco"
},
"type": {
"title": "Tipo",
"placeholder": "Seleziona il tipo di innesco"
"placeholder": "Seleziona il tipo di innesco",
"description": "Si attiva quando viene rilevata una descrizione di un oggetto simile tracciato",
"thumbnail": "Attiva quando viene rilevata una miniatura di un oggetto simile tracciato"
},
"content": {
"title": "Contenuto",
"imagePlaceholder": "Seleziona un'immagine",
"imagePlaceholder": "Seleziona una miniatura",
"textPlaceholder": "Inserisci il contenuto del testo",
"imageDesc": "Seleziona un'immagine per attivare questa azione quando viene rilevata un'immagine simile.",
"imageDesc": "Vengono visualizzate solo le 100 miniature più recenti. Se non riesci a trovare la miniatura desiderata, controlla gli oggetti precedenti in Esplora e imposta un innesco dal menu.",
"textDesc": "Inserisci il testo per attivare questa azione quando viene rilevata una descrizione simile dell'oggetto tracciato.",
"error": {
"required": "Il contenuto è obbligatorio."
@ -813,11 +822,12 @@
"error": {
"min": "La soglia deve essere almeno 0",
"max": "La soglia deve essere al massimo 1"
}
},
"desc": "Imposta la soglia di similarità per questo innesco. Una soglia più alta indica che è necessaria una corrispondenza più vicina per attivare l'innesco."
},
"actions": {
"title": "Azioni",
"desc": "Per impostazione predefinita, Frigate invia un messaggio MQTT per tutti gli inneschi. Scegli un'azione aggiuntiva da eseguire quando questo innesco si attiva.",
"desc": "Per impostazione predefinita, Frigate invia un messaggio MQTT per tutti gli inneschi. Le sottoetichette aggiungono il nome dell'innesco all'etichetta dell'oggetto. Gli attributi sono metadati ricercabili, memorizzati separatamente nei metadati dell'oggetto tracciato.",
"error": {
"min": "È necessario selezionare almeno un'azione."
}
@ -844,6 +854,23 @@
"semanticSearch": {
"title": "La ricerca semantica è disabilitata",
"desc": "Per utilizzare gli inneschi, è necessario abilitare la ricerca semantica."
},
"wizard": {
"title": "Crea innesco",
"step1": {
"description": "Configura le impostazioni di base per il tuo innesco."
},
"step2": {
"description": "Imposta il contenuto che attiverà questa azione."
},
"step3": {
"description": "Configura la soglia e le azioni per questo innesco."
},
"steps": {
"nameAndType": "Nome e tipo",
"configureData": "Configurare i dati",
"thresholdAndActions": "Soglia e azioni"
}
}
},
"roles": {

View File

@ -29,7 +29,7 @@
"false_other": "Dette er ikke en {{label}}"
},
"question": {
"label": "Bekreft denne merkelappen for Frigate Plus",
"label": "Bekreft denne etiketten for Frigate Plus",
"ask_an": "Er dette objekt en <code>{{label}}</code>?",
"ask_a": "Er dette objektet en <code>{{label}}</code>?",
"ask_full": "Er dette objekt en <code>{{untranslatedLabel}}</code> ({{translatedLabel}})?"
@ -56,7 +56,7 @@
}
},
"toast": {
"success": "Eksport startet. Se filen på eksport­siden.",
"success": "Eksport startet. Se filen på eksportsiden.",
"error": {
"failed": "Klarte ikke å starte eksport: {{error}}",
"noVaildTimeSelected": "Ingen gyldig tidsperiode valgt",
@ -124,7 +124,7 @@
"imagePicker": {
"selectImage": "Velg et sporet objekts miniatyrbilde",
"search": {
"placeholder": "Søk etter (under-)merkelapp..."
"placeholder": "Søk etter (under-)etikett..."
},
"noImages": "Ingen miniatyrbilder funnet for dette kameraet",
"unknownLabel": "Lagret utløserbilde"

View File

@ -1,14 +1,14 @@
{
"filter": "Filter",
"labels": {
"label": "Merkelapper",
"label": "Etiketter",
"all": {
"title": "Alle masker / soner",
"short": "Merkelapper"
"short": "Etiketter"
},
"count": "{{count}} merkelapper",
"count_other": "{{count}} Merkelapper",
"count_one": "{{count}} Merkelapp"
"count_other": "{{count}} Etiketter",
"count_one": "{{count}} Etikett"
},
"features": {
"hasVideoClip": "Har et videoklipp",
@ -39,7 +39,7 @@
"title": "Innstillinger",
"defaultView": {
"title": "Standard visning",
"desc": "Når ingen filtre er valgt, vis et sammendrag av de nyeste sporede objektene per merkelapp, eller vis et ufiltrert rutenett.",
"desc": "Når ingen filtre er valgt, vis et sammendrag av de nyeste sporede objektene per etikett, eller vis et ufiltrert rutenett.",
"summary": "Sammendrag",
"unfilteredGrid": "Ufiltrert rutenett"
},
@ -101,8 +101,8 @@
},
"timeRange": "Tidsrom",
"subLabels": {
"label": "Under-Merkelapper",
"all": "Alle under-Merkelapper"
"label": "Underetiketter",
"all": "Alle underetiketter"
},
"score": "Poengsum",
"estimatedSpeed": "Estimert hastighet ({{unit}})",

View File

@ -43,10 +43,17 @@
"trackedObject_one": "objekt",
"trackedObject_other": "objekter",
"noObjectDetailData": "Ingen detaljdata for objektet tilgjengelig.",
"label": "Detalj"
"label": "Detalj",
"settings": "Detaljvisning innstillinger",
"alwaysExpandActive": {
"desc": "Utvid alltid objektdetaljene for det aktive gjennomgangselementet når tilgjengelig.",
"title": "Utvid alltid for aktive"
}
},
"objectTrack": {
"trackedPoint": "Sporingspunkt",
"clickToSeek": "Klikk for å gå til dette tidspunktet"
}
},
"zoomIn": "Zoom inn",
"zoomOut": "Zoom ut"
}

View File

@ -87,7 +87,7 @@
},
"toast": {
"success": {
"updatedSublabel": "Under-merkelapp oppdatert med suksess.",
"updatedSublabel": "Underetikett ble oppdatert.",
"updatedLPR": "Vellykket oppdatering av kjennemerke.",
"regenerate": "En ny beskrivelse har blitt anmodet fra {{provider}}. Avhengig av hastigheten til leverandøren din, kan den nye beskrivelsen ta litt tid å regenerere.",
"audioTranscription": "Lydtranskripsjon ble forespurt."
@ -95,7 +95,7 @@
"error": {
"regenerate": "Feil ved anrop til {{provider}} for en ny beskrivelse: {{errorMessage}}",
"updatedLPRFailed": "Oppdatering av kjennemerke feilet: {{errorMessage}}",
"updatedSublabelFailed": "Feil ved oppdatering av under-merkelapp: {{errorMessage}}",
"updatedSublabelFailed": "Feil ved oppdatering av underetikett: {{errorMessage}}",
"audioTranscription": "Forespørsel om lydtranskripsjon feilet: {{errorMessage}}"
}
},
@ -103,7 +103,7 @@
"tips": {
"mismatch_one": "{{count}} utilgjengelig objekt ble oppdaget og inkludert i dette inspeksjonselementet. Disse objektene kvalifiserte ikke som et varsel eller deteksjon, eller har allerede blitt ryddet opp/slettet.",
"mismatch_other": "{{count}} utilgjengelige objekter ble oppdaget og inkludert i dette inspeksjonselementet. Disse objektene kvalifiserte ikke som et varsel eller deteksjon, eller har allerede blitt ryddet opp/slettet.",
"hasMissingObjects": "Juster konfigurasjonen hvis du vil at Frigate skal lagre sporede objekter for følgende merkelapper: <em>{{objects}}</em>"
"hasMissingObjects": "Juster konfigurasjonen hvis du vil at Frigate skal lagre sporede objekter for følgende etiketter: <em>{{objects}}</em>"
}
},
"topScore": {
@ -126,10 +126,10 @@
},
"regenerateFromThumbnails": "Regenerer fra miniatyrbilder",
"tips": {
"descriptionSaved": "Beskrivelse lagret med suksess",
"descriptionSaved": "Beskrivelsen ble lagret",
"saveDescriptionFailed": "Feil ved lagring av beskrivelse: {{errorMessage}}"
},
"label": "Merkelapp",
"label": "Etikett",
"editLPR": {
"title": "Rediger kjennemerke",
"descNoLabel": "Skriv inn et nytt kjennemerke for dette sporede objekt",
@ -142,9 +142,9 @@
"expandRegenerationMenu": "Utvid regenereringsmenyen",
"regenerateFromSnapshot": "Regenerer fra øyeblikksbilde",
"editSubLabel": {
"title": "Rediger under-merkelapp",
"desc": "Angi en ny under-merkelapp for denne {{label}}",
"descNoLabel": "Angi en ny under-merkelapp for dette sporede objektet"
"title": "Rediger underetikett",
"desc": "Angi en ny underetikett for denne {{label}}",
"descNoLabel": "Angi en ny underetikett for dette sporede objektet"
},
"snapshotScore": {
"label": "Øyeblikksbilde poengsum"
@ -204,7 +204,7 @@
"deleteTrackedObject": {
"toast": {
"error": "Feil ved sletting av sporet objekt: {{errorMessage}}",
"success": "Sporet objekt ble slettet med suksess."
"success": "Sporet objekt ble slettet."
}
},
"tooltip": "Samsvarer {{type}} til {{confidence}}%"
@ -268,7 +268,7 @@
},
"offset": {
"label": "Annoteringsforskyvning",
"desc": "Disse dataene kommer fra kameraets deteksjonsstrøm, men legges over bilder fra opptaksstrømmen. Det er usannsynlig at de to strømmene er perfekt synkronisert. Som et resultat vil avgrensningsboksen og opptaket ikke stemme perfekt overens. Feltet <code>annotation_offset</code> kan imidlertid brukes til å justere dette.",
"desc": "Disse dataene kommer fra kameraets deteksjonsstrøm, men legges over bilder fra opptaksstrømmen. Det er lite sannsynlig at de to strømmene er perfekt synkronisert. Som et resultat vil avgrensningsboksen og opptaket ikke stemme perfekt overens. Du kan bruke denne innstillingen til å forskyve annoteringene fremover eller bakover i tid for å tilpasse dem bedre til det innspilte opptaket.",
"millisecondsToOffset": "Antall millisekunder deteksjonsannoteringene skal forskyves med. <em>Standard: 0</em>",
"tips": "TIPS: Se for deg et hendelsesklipp med en person som går fra venstre mot høyre. Hvis avgrensningsboksen på tidslinjen for hendelsen konsekvent er til venstre for personen, bør verdien reduseres. På samme måte, hvis en person går fra venstre mot høyre og avgrensningsboksen konsekvent er foran personen, bør verdien økes.",
"toast": {

View File

@ -71,15 +71,15 @@
"label": "Vis statistikk",
"desc": "Aktiver dette alternativet for å vise strømmestatistikk som et overlegg på kamerastrømmen."
},
"started": "Startet manuelt opptak på forespørsel.",
"end": "Avslutt opptak på forespørsel",
"title": "På forespørsel",
"started": "Startet manuelt opptak.",
"end": "Avslutt manuelt opptak",
"title": "Manuelt opptak",
"debugView": "Feilsøkingsvisning",
"start": "Start opptak på forespørsel",
"failedToStart": "Kunne ikke starte manuelt opptak på forespørsel.",
"start": "Start manuelt opptak",
"failedToStart": "Kunne ikke starte manuelt opptak.",
"recordDisabledTips": "Siden opptak er deaktivert eller begrenset i konfigurasjonen for dette kameraet, vil kun et øyeblikksbilde bli lagret.",
"ended": "Avsluttet manuelt opptak på forespørsel.",
"failedToEnd": "Kunne ikke avslutte manuelt opptak på forespørsel."
"ended": "Avsluttet manuelt opptak.",
"failedToEnd": "Kunne ikke avslutte manuelt opptak."
},
"audio": "Lyd",
"suspend": {

View File

@ -12,14 +12,14 @@
"filter": {
"label": {
"cameras": "Kameraer",
"labels": "Merkelapper",
"labels": "Etiketter",
"search_type": "Søketype",
"after": "Etter",
"min_score": "Min. poengsum",
"max_score": "Maks. poengsum",
"min_speed": "Min. hastighet",
"zones": "Soner",
"sub_labels": "Under-merkelapper",
"sub_labels": "Underetiketter",
"time_range": "Tidsintervall",
"before": "Før",
"max_speed": "Maks. hastighet",

View File

@ -50,6 +50,10 @@
"automaticLiveView": {
"label": "Automatisk direktevisning",
"desc": "Bytt automatisk til et kameras direktevisning når aktivitet oppdages. Deaktivering av dette valget gjør at statiske kamerabilder i Direkte-dashbord kun oppdateres én gang i minuttet."
},
"displayCameraNames": {
"label": "Vis alltid kameranavn",
"desc": "Vis alltid kameranavnene i en merkelapp i dashbordet for direktevisning med flere kameraer."
}
},
"storedLayouts": {
@ -681,12 +685,12 @@
"enrichments": {
"title": "Innstillinger for utvidelser",
"licensePlateRecognition": {
"desc": "Frigate kan gjenkjenne kjennemerker på kjøretøy og automatisk legge til de oppdagede tegnene i feltet \"recognized_license_plate\", eller et kjent navn som en under-merkelapp på objekter av typen bil. Et vanlig brukstilfelle kan være å lese kjennemerker på biler som kjører inn i en innkjørsel eller biler som passerer på en gate.",
"desc": "Frigate kan gjenkjenne kjennemerker på kjøretøy og automatisk legge til de oppdagede tegnene i feltet \"recognized_license_plate\", eller et kjent navn som en underetikett på objekter av typen bil. Et vanlig brukstilfelle kan være å lese kjennemerker på biler som kjører inn i en innkjørsel eller biler som passerer på en gate.",
"title": "Kjennemerke gjenkjenning",
"readTheDocumentation": "Se dokumentasjonen"
},
"birdClassification": {
"desc": "Fugleklassifisering identifiserer kjente fugler ved hjelp av en kvantisert TensorFlow-modell. Når en fugl gjenkjennes, vil det vanlige navnet legges til som en under-merkelapp. Denne informasjonen vises i brukergrensesnittet, filtre, samt i meldingsvarsler.",
"desc": "Fugleklassifisering identifiserer kjente fugler ved hjelp av en kvantisert TensorFlow-modell. Når en fugl gjenkjennes, vil det vanlige navnet legges til som en underetikett. Denne informasjonen vises i brukergrensesnittet, filtre, samt i meldingsvarsler.",
"title": "Klassifisering av fugler"
},
"semanticSearch": {
@ -730,7 +734,7 @@
}
},
"title": "Ansiktsgjenkjenning",
"desc": "Ansiktsgjenkjenning gjør det mulig å tildele navn til personer, og når ansiktet deres gjenkjennes, vil Frigate tildele personens navn som en under-merkelapp. Denne informasjonen vises i brukergrensesnittet, filtre, samt i meldingsvarsler.",
"desc": "Ansiktsgjenkjenning gjør det mulig å tildele navn til personer, og når ansiktet deres gjenkjennes, vil Frigate tildele personens navn som en underetikett. Denne informasjonen vises i brukergrensesnittet, filtre, samt i meldingsvarsler.",
"readTheDocumentation": "Se dokumentasjonen"
},
"unsavedChanges": "Ulagrede endringer i innstillinger for utvidelser",
@ -764,7 +768,9 @@
},
"actions": {
"alert": "Marker som varsel",
"notification": "Send meldingsvarsel"
"notification": "Send meldingsvarsel",
"sub_label": "Legg til underetikett",
"attribute": "Legg til attributt"
},
"dialog": {
"createTrigger": {
@ -819,7 +825,7 @@
},
"actions": {
"title": "Handlinger",
"desc": "Som standard sender Frigate en MQTT-melding for alle utløsere. Velg en ekstra handling som skal utføres når denne utløseren aktiveres.",
"desc": "Som standard sender Frigate en MQTT-melding for alle utløsere. Underetiketter legger til navnet på utløseren i objektetiketten. Attributter er søkbare metadata som lagres separat i objektets sporingsmetadata.",
"error": {
"min": "Minst én handling må velges."
}

View File

@ -43,7 +43,8 @@
},
"train": {
"title": "Nylige klassifiseringer",
"aria": "Velg nylige klassifiseringer"
"aria": "Velg nylige klassifiseringer",
"titleShort": "Nylig"
},
"categories": "Kategorier",
"createCategory": {

View File

@ -43,7 +43,8 @@
},
"train": {
"title": "Recente classificaties",
"aria": "Selecteer recente classificaties"
"aria": "Selecteer recente classificaties",
"titleShort": "Recent"
},
"categories": "Klassen",
"createCategory": {
@ -98,7 +99,8 @@
"stateRequiresTwoClasses": "Toestandsmodellen vereisen minimaal 2 klassen",
"objectLabelRequired": "Selecteer een objectlabel",
"objectTypeRequired": "Selecteer een classificatietype"
}
},
"states": "Staten"
},
"step2": {
"description": "Selecteer cameras en definieer voor elke camera het te monitoren gebied. Het model zal de toestand van deze gebieden classificeren.",

View File

@ -43,10 +43,17 @@
"trackedObject_one": "object",
"trackedObject_other": "objecten",
"noObjectDetailData": "Geen objectdetails beschikbaar.",
"label": "Detail"
"label": "Detail",
"settings": "Instellingen voor detailweergave",
"alwaysExpandActive": {
"desc": "Altijd de objectdetails van het actieve beoordelingsitem uitklappen wanneer deze beschikbaar zijn.",
"title": "Het huidige item altijd uitvouwen"
}
},
"objectTrack": {
"trackedPoint": "Gevolgd punt",
"clickToSeek": "Klik om naar deze tijd te zoeken"
}
},
"zoomIn": "Zoom in",
"zoomOut": "Zoom uit"
}

View File

@ -268,7 +268,7 @@
},
"offset": {
"label": "Annotatie-afwijking",
"desc": "Deze gegevens zijn afkomstig van de detectiestream van je camera, maar worden weergegeven op beelden uit de opnamestream. Het is onwaarschijnlijk dat deze twee streams perfect gesynchroniseerd zijn. Hierdoor zullen het objectkader en het beeld niet exact op elkaar aansluiten. Het veld <code>annotation_offset</code> kan echter worden gebruikt om deze annotatie-afwijking te corrigeren.",
"desc": "Deze gegevens zijn afkomstig van de detectiestream van je camera, maar worden weergegeven op beelden uit de opnamestream. Het is onwaarschijnlijk dat deze twee streams perfect gesynchroniseerd zijn. Hierdoor zullen het objectkader en het beeld niet exact op elkaar aansluiten. Met deze instelling kun je de annotaties vooruit of achteruit in de tijd verschuiven om ze beter uit te lijnen met het opgenomen beeldmateriaal.",
"millisecondsToOffset": "Aantal milliseconden om objectkader mee te verschuiven. <em>Standaard: 0</em>",
"tips": "TIP: Stel je voor dat er een clip is waarin een persoon van links naar rechts loopt. Als het objectkader in de tijdlijn van de activiteit steeds links van de persoon ligt, dan moet de waarde verlaagd worden. Op dezelfde manier als het objectkader consequent vóór de persoon ligt dus vooruitloopt, moet de waarde verhoogd worden.",
"toast": {

View File

@ -50,6 +50,10 @@
"playAlertVideos": {
"label": "Meldingen afspelen",
"desc": "Standaard worden recente meldingen op het Live dashboard afgespeeld als kleine lusvideo's. Schakel deze optie uit om alleen een statische afbeelding van recente meldingen weer te geven op dit apparaat/browser."
},
"displayCameraNames": {
"label": "Altijd cameranamen weergeven",
"desc": "Toon altijd de cameranamen in een label op het live-cameradashboard."
}
},
"title": "Algemene instellingen",
@ -764,7 +768,9 @@
},
"actions": {
"alert": "Markeren als waarschuwing",
"notification": "Melding verzenden"
"notification": "Melding verzenden",
"sub_label": "Sublabel toevoegen",
"attribute": "Attribuut toevoegen"
},
"dialog": {
"createTrigger": {
@ -819,7 +825,7 @@
},
"actions": {
"title": "Acties",
"desc": "Standaard verstuurt Frigate een MQTT-bericht voor alle triggers. Kies een extra actie die moet worden uitgevoerd wanneer deze trigger wordt geactiveerd.",
"desc": "Standaard stuurt Frigate een MQTT-bericht voor alle triggers. Sublabels voegen de triggernaam toe aan het objectlabel. Attributen zijn doorzoekbare metadata die afzonderlijk worden opgeslagen in de metadata van het gevolgde object.",
"error": {
"min": "Er moet ten minste één actie worden geselecteerd."
}

View File

@ -40,8 +40,8 @@
"detail": {
"noDataFound": "Nenhum dado de detalhe para revisar",
"aria": "Alternar visualização de detalhe",
"trackedObject_one": "objeto rastreado",
"trackedObject_other": "objetos rastreados",
"trackedObject_one": "objeto",
"trackedObject_other": "objetos",
"noObjectDetailData": "Nenhum dado de detalhe de objeto disponível.",
"label": "Detalhe"
},

View File

@ -237,7 +237,10 @@
}
},
"label": {
"back": "Mergi înapoi"
"back": "Mergi înapoi",
"hide": "Ascunde {{item}}",
"show": "Afișează {{item}}",
"ID": "ID"
},
"selectItem": "Selectează {{item}}",
"pagination": {
@ -281,5 +284,13 @@
"readTheDocumentation": "Citește documentația",
"information": {
"pixels": "{{area}}px"
},
"list": {
"two": "{{0}} și {{1}}",
"many": "{{items}}, și {{last}}"
},
"field": {
"optional": "Opțional",
"internalID": "ID-ul Intern pe care Frigate îl folosește în configurație și în baza de date"
}
}

View File

@ -83,7 +83,7 @@
"export": "Exportă",
"selectOrExport": "Selectează sau exportă",
"toast": {
"success": "Exportul a început cu succes. Vizualizați fișierul în dosarul /exports.",
"success": "Exportul a început cu succes. Vizualizați fișierul pe pagina de exporturi.",
"error": {
"failed": "Eroare la pornirea exportului: {{error}}",
"endTimeMustAfterStartTime": "Ora de sfârșit trebuie să fie după ora de început",
@ -129,6 +129,7 @@
"search": {
"placeholder": "Caută după etichetă sau subetichetă..."
},
"noImages": "Nu s-au găsit miniaturi pentru această cameră"
"noImages": "Nu s-au găsit miniaturi pentru această cameră",
"unknownLabel": "Imaginea declanșator salvată"
}
}

View File

@ -1 +1,136 @@
{}
{
"documentTitle": "Modele de clasificare",
"button": {
"deleteClassificationAttempts": "Șterge imaginile de clasificare",
"renameCategory": "Redenumește clasa",
"deleteCategory": "Șterge clasa",
"deleteImages": "Șterge imaginile",
"trainModel": "Antrenează modelul"
},
"toast": {
"success": {
"deletedCategory": "Clasă ștearsă",
"deletedImage": "Imagini șterse",
"categorizedImage": "Imagine clasificată cu succes",
"trainedModel": "Model antrenat cu succes.",
"trainingModel": "Antrenamentul modelului a fost pornit cu succes."
},
"error": {
"deleteImageFailed": "Ștergerea a eșuat: {{errorMessage}}",
"deleteCategoryFailed": "Ștergerea clasei a eșuat: {{errorMessage}}",
"categorizeFailed": "Categorisirea imaginii a eșuat: {{errorMessage}}",
"trainingFailed": "Pornirea antrenamentului modelului a eșuat: {{errorMessage}}"
}
},
"deleteCategory": {
"title": "Șterge clasa",
"desc": "Sigur doriți să ștergeți clasa {{name}}? Aceasta va șterge permanent toate imaginile asociate și va necesita reantrenarea modelului."
},
"deleteDatasetImages": {
"title": "Șterge imaginile setului de date",
"desc": "Sigur doriți să ștergeți {{count}} imagini din {{dataset}}? Această acțiune nu poate fi anulată și va necesita reantrenarea modelului."
},
"deleteTrainImages": {
"title": "Șterge imaginile de antrenament",
"desc": "Sigur doriți să ștergeți {{count}} imagini? Această acțiune nu poate fi anulată."
},
"renameCategory": {
"title": "Redenumește clasa",
"desc": "Introduceți un nume nou pentru {{name}}. Va trebui să reantrenați modelul pentru ca modificarea numelui să aibă efect."
},
"description": {
"invalidName": "Nume invalid. Numele pot include doar litere, cifre, spații, apostrofuri, underscore-uri și liniuțe."
},
"train": {
"title": "Clasificări recente",
"titleShort": "Recente",
"aria": "Selectează clasificările recente"
},
"categories": "Clase",
"createCategory": {
"new": "Creează clasă nouă"
},
"categorizeImageAs": "Clasifică imaginea ca:",
"categorizeImage": "Clasifică imaginea",
"noModels": {
"object": {
"title": "Nu există modele de clasificare a obiectelor",
"description": "Creează un model personalizat pentru a clasifica obiectele detectate.",
"buttonText": "Creează model de obiect"
},
"state": {
"title": "Nu există modele de clasificare a stării",
"description": "Creează un model personalizat pentru a monitoriza și clasifica schimbările de stare în anumite zone ale camerei.",
"buttonText": "Creează model de stare"
}
},
"wizard": {
"title": "Creează clasificare nouă",
"steps": {
"nameAndDefine": "Numire și definire",
"stateArea": "Zona de stare",
"chooseExamples": "Alege exemple"
},
"step1": {
"description": "Modelele de stare monitorizează zone fixe ale camerei pentru schimbări (de exemplu, ușă deschisă/închisă). Modelele de obiect adaugă clasificări obiectelor detectate (de exemplu, animale cunoscute, curieri etc.).",
"name": "Nume",
"namePlaceholder": "Introduceți numele modelului...",
"type": "Tip",
"typeState": "Stare",
"typeObject": "Obiect",
"objectLabel": "Etichetă obiect",
"objectLabelPlaceholder": "Selectează tipul obiectului...",
"classificationType": "Tip de clasificare",
"classificationTypeTip": "Află despre tipurile de clasificare",
"classificationTypeDesc": "Subetichetele adaugă text suplimentar la eticheta obiectului (de exemplu, 'Persoană: UPS'). Atributele sunt metadate căutabile, stocate separat în metadatele obiectului.",
"classificationSubLabel": "Subeticheta",
"classificationAttribute": "Atribut",
"classes": "Clase",
"classesTip": "Află despre clase",
"classesStateDesc": "Definește diferitele stări în care poate fi zona camerei tale. De exemplu: 'deschis' și 'închis' pentru o ușă de garaj.",
"classesObjectDesc": "Definește diferitele categorii în care să fie clasificate obiectele detectate. De exemplu: 'curier', 'rezident', 'necunoscut' pentru clasificarea persoanelor.",
"classPlaceholder": "Introduceți numele clasei...",
"errors": {
"nameRequired": "Numele modelului este obligatoriu",
"nameLength": "Numele modelului trebuie să aibă 64 de caractere sau mai puțin",
"nameOnlyNumbers": "Numele modelului nu poate conține doar cifre",
"classRequired": "Este necesară cel puțin 1 clasă",
"classesUnique": "Numele claselor trebuie să fie unice",
"stateRequiresTwoClasses": "Modelele de stare necesită cel puțin 2 clase",
"objectLabelRequired": "Vă rugăm să selectați o etichetă de obiect",
"objectTypeRequired": "Vă rugăm să selectați un tip de clasificare"
}
},
"step2": {
"description": "Selectați camerele și definiți zona de monitorizat pentru fiecare cameră. Modelul va clasifica starea acestor zone.",
"cameras": "Camere",
"selectCamera": "Selectează camera",
"noCameras": "Apasă pe + pentru a adăuga camere",
"selectCameraPrompt": "Selectați o cameră din listă pentru a defini aria sa de monitorizare"
},
"step3": {
"selectImagesPrompt": "Selectați toate imaginile cu: {{className}}",
"selectImagesDescription": "Apăsați pe imagini pentru a le selecta. Apăsați pe Continuare când ați terminat cu această clasă.",
"generating": {
"title": "Generare imagini de exemplu",
"description": "Frigate preia imagini reprezentative din înregistrările tale. Aceasta poate dura câteva momente..."
},
"training": {
"title": "Antrenare model",
"description": "Modelul tău este antrenat în fundal. Închide această fereastră și modelul va începe să ruleze imediat ce antrenamentul este finalizat."
},
"retryGenerate": "Reîncearcă generarea",
"noImages": "Nu s-au generat imagini de exemplu",
"classifying": "Clasificare și antrenare...",
"trainingStarted": "Antrenamentul a început cu succes",
"errors": {
"noCameras": "Nu există camere configurate",
"noObjectLabel": "Nu a fost selectată nicio etichetă de obiect",
"generateFailed": "Generarea exemplelor a eșuat: {{error}}",
"generationFailed": "Generarea a eșuat. Vă rugăm să încercați din nou.",
"classifyFailed": "Clasificarea imaginilor a eșuat: {{error}}"
},
"generateSuccess": "Imaginile de exemplu au fost generate cu succes"
}
}
}

View File

@ -40,12 +40,20 @@
"detail": {
"noDataFound": "Nicio dată detaliată de revizuit",
"aria": "Comută vizualizarea detaliată",
"trackedObject_one": "obiect urmărit",
"trackedObject_other": "obiecte urmărite",
"noObjectDetailData": "Nicio dată de detaliu obiect disponibilă."
"trackedObject_one": "obiect",
"trackedObject_other": "obiecte",
"noObjectDetailData": "Nicio dată de detaliu obiect disponibilă.",
"label": "Detaliu",
"settings": "Setări vizualizare detaliată",
"alwaysExpandActive": {
"title": "Extinde întotdeauna activul",
"desc": "Extinde întotdeauna detaliile obiectului elementului activ de revizuire, atunci când sunt disponibile."
}
},
"objectTrack": {
"trackedPoint": "Punct urmărit",
"clickToSeek": "Fă clic pentru a naviga la acest moment"
}
"clickToSeek": "Apasă pentru a naviga la acest moment"
},
"zoomIn": "Mărește",
"zoomOut": "Micșorează"
}

View File

@ -200,12 +200,22 @@
"audioTranscription": {
"label": "Transcrie",
"aria": "Solicită transcrierea audio"
},
"viewTrackingDetails": {
"label": "Vizualizați detaliile de urmărire",
"aria": "Vizualizați detaliile de urmărire"
},
"showObjectDetails": {
"label": "Afișează traseul obiectului"
},
"hideObjectDetails": {
"label": "Ascunde traseul obiectului"
}
},
"dialog": {
"confirmDelete": {
"title": "Confirmă ștergerea",
"desc": "Ștergerea acestui obiect urmărit elimină instantaneul, orice încorporări salvate și orice intrări asociate ciclului de viață al obiectului. Materialul video înregistrat al acestui obiect urmărit în vizualizarea Istoric <em>NU</em> va fi șters.<br /><br />Ești sigur că vrei să continui?"
"desc": "Ștergerea acestui obiect urmărit elimină snapshot-ul, orice încorporări salvate și orice intrări asociate detaliilor de urmărire. Materialul video înregistrat al acestui obiect urmărit în vizualizarea Istoric <em>NU</em> va fi șters.<br /><br />Ești sigur că vrei să continui?"
}
},
"noTrackedObjects": "Nu au fost găsite obiecte urmărite",
@ -224,5 +234,53 @@
},
"concerns": {
"label": "Îngrijorări"
},
"trackingDetails": {
"title": "Detalii de Urmărire",
"noImageFound": "Nu s-a găsit nicio imagine pentru acest marcaj de timp.",
"createObjectMask": "Creează Masca Obiectului",
"adjustAnnotationSettings": "Ajustează Setările de anotare",
"scrollViewTips": "Derulează pentru a vizualiza momentele semnificative din ciclul de viață al acestui obiect.",
"autoTrackingTips": "Pozițiile casetelor de delimitare vor fi inexacte pentru camerele cu urmărire automată.",
"count": "{{first}} din {{second}}",
"trackedPoint": "Punct Urmărit",
"lifecycleItemDesc": {
"visible": "detectat {{label}}",
"entered_zone": "{{label}} a intrat în {{zones}}",
"active": "{{label}} a devenit activ",
"stationary": "{{label}} a devenit staționar",
"attribute": {
"faceOrLicense_plate": "{{attribute}} detectat pentru {{label}}",
"other": "{{label}} recunoscut ca {{attribute}}"
},
"gone": "{{label}} a plecat",
"heard": "{{label}} auzit",
"external": "{{label}} detectat",
"header": {
"zones": "Zone",
"ratio": "Raport",
"area": "Aria"
}
},
"annotationSettings": {
"title": "Setări de adnotare",
"showAllZones": {
"title": "Afișează toate",
"desc": "Afișează întotdeauna zonele pe cadrele în care obiectele au intrat într-o zonă."
},
"offset": {
"label": "Compensare adnotare",
"desc": "Aceste date provin din fluxul de detectare al camerei tale, dar sunt suprapuse pe imaginile din fluxul de înregistrare. Este puțin probabil ca cele două fluxuri să fie perfect sincronizate. Drept urmare, caseta delimitatoare și materialul video nu se vor alinia perfect. Poți folosi această setare pentru a decală adnotările înainte sau înapoi în timp, pentru a le alinia mai bine cu materialul înregistrat.",
"millisecondsToOffset": "Millisecunde pentru a decalca adnotările de detectare. <em>Implicit: 0</em>",
"tips": "SFAT: Imaginează-ți că există un clip al unui eveniment cu o persoană care merge de la stânga la dreapta. Dacă caseta delimitatoare a cronologiei evenimentului este constant în stânga persoanei, atunci valoarea ar trebui să fie scăzută. În mod similar, dacă o persoană merge de la stânga la dreapta și caseta delimitatoare este constant în fața persoanei, atunci valoarea ar trebui să fie crescută.",
"toast": {
"success": "Decalajul de adnotare pentru {{camera}} a fost salvat în fișierul de configurare. Repornește Frigate pentru a aplica modificările."
}
}
},
"carousel": {
"previous": "Slide-ul anterior",
"next": "Slide-ul următor"
}
}
}

View File

@ -13,5 +13,11 @@
"error": {
"renameExportFailed": "Eroare redenumire export: {{errorMessage}}"
}
},
"tooltip": {
"shareExport": "Partajează exportul",
"downloadVideo": "Descarcă videoclipul",
"editName": "Editează numele",
"deleteExport": "Șterge exportul"
}
}

View File

@ -1,8 +1,8 @@
{
"description": {
"addFace": "Parcurge adăugarea unei colecții noi la biblioteca de fețe.",
"addFace": "Adaugă o colecție nouă în Biblioteca de fețe încărcând prima ta imagine.",
"placeholder": "Introduceti un nume pentru aceasta colectie",
"invalidName": "Nume invalid. Numele poate conține doar litere, cifre, spații, apostrofuri, liniuțe de subliniere și cratime."
"invalidName": "Nume invalid. Numele pot include doar litere, cifre, spații, apostrofuri, underscore-uri și liniuțe."
},
"details": {
"person": "Persoană",

View File

@ -17,9 +17,9 @@
},
"move": {
"clickMove": {
"label": "Fă click în cadrul imaginii pentru a centra camera",
"enable": "Activează clic pentru a muta",
"disable": "Dezactivează clic pentru a muta"
"label": "Apasă în cadrul imaginii pentru a centra camera",
"enable": "Activează mutarea prin clic",
"disable": "Dezactivează mutarea prin clic"
},
"left": {
"label": "Mișcă camera PTZ spre stânga"
@ -36,7 +36,7 @@
},
"frame": {
"center": {
"label": "Fă clic în cadru pentru a centra camera PTZ"
"label": "Apasă în cadru pentru a centra camera PTZ"
}
},
"presets": "Presetări cameră PTZ",
@ -100,7 +100,7 @@
"start": "Pornește înregistrarea la cerere",
"started": "Înregistrare la cerere pornită manual.",
"failedToStart": "Nu s-a putut porni înregistrarea manuală la cerere.",
"recordDisabledTips": "Deoarece înregistrarea este dezactivată sau restricționată în configurația pentru această cameră, va fi salvată doar o captură de ecran.",
"recordDisabledTips": "Deoarece înregistrarea este dezactivată sau restricționată în configurația pentru această cameră, doar un snapshot va fi salvat.",
"end": "Oprește înregistrarea la cerere",
"ended": "Înregistrarea manuală la cerere s-a încheiat.",
"failedToEnd": "Nu s-a reușit încheierea înregistrării manuale la cerere."

View File

@ -33,7 +33,7 @@
"step1": "Tastează un nume de filtru urmat de două puncte (ex. „camere:” ).",
"step3": "Folosește mai multe filtre adăugându-le unul după altul, separate prin spațiu.",
"step4": "Filtrele de dată (înainte: și după:) folosesc formatul {{DateFormat}}.",
"step6": "Elimină filtrele făcând clic pe „X”-ul de lângă ele.",
"step6": "Elimină filtrele apăsând pe „X”-ul de lângă ele.",
"exampleLabel": "Exemplu:",
"step5": "Filtrul pentru intervalul de timp folosește formatul {{exampleTime}}.",
"step2": "Selectează o valoare din sugestii sau tastează propria valoare.",

View File

@ -267,7 +267,7 @@
"desc": "Specifică o viteză minimă pe care trebuie să o aibă obiectele pentru a fi considerate în această zonă."
},
"documentTitle": "Editează zone - Frigate",
"clickDrawPolygon": "Click pentru a desena un poligon pe imagine.",
"clickDrawPolygon": "Apasă pentru a desena un poligon pe imagine.",
"toast": {
"success": "Zona ({{zoneName}}) a fost salvată. Repornește Frigate pentru a aplica modificările."
},
@ -282,7 +282,7 @@
"point_one": "{{count}} punct",
"point_few": "{{count}} puncte",
"point_other": "{{count}} de puncte",
"clickDrawPolygon": "Fă click pentru a desena un poligon pe imagine.",
"clickDrawPolygon": "Fă clic pentru a desena un poligon pe imagine.",
"label": "Măști de mișcare",
"documentTitle": "Editează masca de mișcare - Frigate",
"desc": {
@ -330,7 +330,7 @@
"title": "{{polygonName}} a fost salvat. Repornește Frigate pentru a aplica modificările."
}
},
"clickDrawPolygon": "Fă click pentru a desena un poligon pe imagine.",
"clickDrawPolygon": "Fă clic pentru a desena un poligon pe imagine.",
"context": "Măștile de filtrare a obiectelor sunt folosite pentru a elimina falsele pozitive pentru un anumit tip de obiect, în funcție de locația acestuia."
},
"restart_required": "Repornire necesară (măști/zone modificate)",
@ -682,7 +682,7 @@
"triggers": {
"documentTitle": "Declanșatoare",
"management": {
"title": "Gestionarea declanșatoarelor",
"title": "Declanșatoare",
"desc": "Gestionează declanșatoarele pentru {{camera}}. Folosește tipul miniatură pentru a declanșa pe miniaturi similare cu obiectul urmărit selectat și tipul descriere pentru a declanșa pe descrieri similare textului pe care îl specifici."
},
"addTrigger": "Adaugă declanșator",
@ -721,25 +721,28 @@
"form": {
"name": {
"title": "Nume",
"placeholder": "Introdu numele declanșatorului",
"placeholder": "Denumește acest declanșator",
"error": {
"minLength": "Numele trebuie să aibă cel puțin 2 caractere.",
"invalidCharacters": "Numele poate conține doar litere, cifre, underscore-uri și cratime.",
"minLength": "Câmpul trebuie să aibă cel puțin 2 caractere.",
"invalidCharacters": "Câmpul poate conține doar litere, cifre, underscore-uri și cratime.",
"alreadyExists": "Un declanșator cu acest nume există deja pentru această cameră."
}
},
"description": "Introduceți un nume sau o descriere unică pentru a identifica acest declanșator"
},
"enabled": {
"description": "Activează sau dezactivează acest declanșator"
},
"type": {
"title": "Tip",
"placeholder": "Selectează tipul de declanșator"
"placeholder": "Selectează tipul de declanșator",
"description": "Declanșează atunci când este detectată o descriere de obiect urmărit similară",
"thumbnail": "Declanșează atunci când este detectată o miniatură de obiect urmărit similară"
},
"content": {
"title": "Conținut",
"imagePlaceholder": "Selectează o imagine",
"imagePlaceholder": "Selectează o miniatură",
"textPlaceholder": "Introdu conținutul textului",
"imageDesc": "Selectează o imagine pentru a declanșa această acțiune atunci când o imagine similară este detectată.",
"imageDesc": "Sunt afișate doar ultimele 100 de miniaturi. Dacă nu găsiți miniatura dorită, vă rugăm să verificați obiectele anterioare în Explorator și să configurați un declanșator din meniul de acolo.",
"textDesc": "Introduceți textul pentru a declanșa această acțiune atunci când este detectată o descriere de obiect urmărit similară.",
"error": {
"required": "Conținutul este obligatoriu."
@ -750,7 +753,8 @@
"error": {
"min": "Pragul trebuie să fie cel puțin 0",
"max": "Pragul trebuie să fie cel mult 1"
}
},
"desc": "Setați pragul de similitudine pentru acest declanșator. Un prag mai mare înseamnă că este necesară o potrivire mai apropiată pentru declanșarea acestuia."
},
"actions": {
"title": "Acțiuni",
@ -781,6 +785,23 @@
"semanticSearch": {
"title": "Căutarea semantică este dezactivată",
"desc": "Căutarea semantică trebuie să fie activată pentru a utiliza declanșatoarele."
},
"wizard": {
"title": "Creează declanșator",
"step1": {
"description": "Configurează setările de bază pentru declanșatorul tău."
},
"step2": {
"description": "Configurează conținutul care va declanșa această acțiune."
},
"step3": {
"description": "Configurează pragul și acțiunile pentru acest declanșator."
},
"steps": {
"nameAndType": "Nume și Tip",
"configureData": "Configurează datele",
"thresholdAndActions": "Prag și Acțiuni"
}
}
},
"roles": {
@ -896,11 +917,16 @@
"invalidCharacters": "Numele camerei conține caractere nevalide",
"nameExists": "Numele camerei există deja",
"brands": {
"reolink-rtsp": "RTSP Reolink nu este recomandat. Se recomandă să activezi HTTP în setările camerei și să repornești asistentul de cameră."
}
"reolink-rtsp": "RTSP Reolink nu este recomandat. Activează HTTP în setările firmware ale camerei și repornește asistentul."
},
"customUrlRtspRequired": "URL-urile personalizate trebuie să înceapă cu \"rtsp://\". Este necesară configurare manuală pentru stream-urile de cameră non-RTSP."
},
"docs": {
"reolink": "https://docs.frigate.video/configuration/camera_specific.html#reolink-cameras"
},
"testing": {
"probingMetadata": "Sondare metadate cameră...",
"fetchingSnapshot": "Preluare snapshot cameră..."
}
},
"step2": {

View File

@ -432,5 +432,72 @@
"shofar": "Šofar",
"liquid": "Kvapalina",
"splash": "Šplechnutie",
"slosh": "Slosh"
"slosh": "Slosh",
"squish": "Vytlačiť",
"drip": "Kvapkať",
"pour": "Nalej",
"trickle": "Pokvapkať",
"gush": "Striekať",
"fill": "Vyplňte",
"spray": "Striekajte",
"pump": "Pumpa",
"stir": "Miešajte",
"boiling": "Varenie",
"sonar": "Sonar",
"arrow": "Šípka",
"whoosh": "Whoosh",
"thump": "Palec",
"thunk": "Thunk",
"electronic_tuner": "Elektronický tuner",
"effects_unit": "Efektuje jednotky",
"chorus_effect": "Zborový efekt",
"basketball_bounce": "Odrážanie basketbalovej lopty",
"bang": "Bang",
"slap": "Buchnutie",
"whack": "Odpáliť",
"smash": "Rozbiť",
"breaking": "Prelomenie",
"bouncing": "Odskakovanie",
"whip": "Bič",
"flap": "Klapka",
"scratch": "Poškriabanie",
"scrape": "Škrabať",
"rub": "Potrieť",
"roll": "Rolovať",
"crushing": "Rozdrvovanie",
"crumpling": "Mačkanie",
"tearing": "Trhanie",
"beep": "Pípnutie",
"ping": "Ping",
"ding": "Ding",
"clang": "Zvonenie",
"squeal": "Kňučať",
"creak": "Vŕzganie",
"rustle": "Šuchot",
"whir": "Vrčanie",
"clatter": "Cvakať",
"sizzle": "Syčať",
"clicking": "Klikanie",
"clickety_clack": "Klikanie kľak",
"rumble": "Rachot",
"plop": "Prasknutie",
"hum": "Hmkanie",
"zing": "Zing",
"boing": "Boing",
"crunch": "Chrumnutie",
"sine_wave": "Sínusoida",
"harmonic": "Harmonický",
"chirp_tone": "Cvrlikací tón",
"pulse": "Pulz",
"inside": "Vnútri",
"outside": "Vonku",
"reverberation": "Dozvuk",
"echo": "Ozvena",
"noise": "Zvuk",
"mains_hum": "Hlavné Hum",
"distortion": "Skreslenie",
"sidetone": "Vedľajší tón",
"cacophony": "Kakofónia",
"throbbing": "Pulzujúci",
"vibration": "Vibrácia"
}

View File

@ -101,7 +101,10 @@
},
"readTheDocumentation": "Prečítajte si dokumentáciu",
"label": {
"back": "Choď späť"
"back": "Choď späť",
"hide": "Skryť {{item}}",
"show": "Zobraziť {{item}}",
"ID": "ID"
},
"button": {
"apply": "Použiť",
@ -281,5 +284,13 @@
},
"information": {
"pixels": "{{area}}px"
},
"list": {
"two": "{{0}} a {{1}}",
"many": "{{items}}, a {{last}}"
},
"field": {
"optional": "Voliteľné",
"internalID": "Interné ID Frigate používa v konfigurácii a databáze"
}
}

View File

@ -53,7 +53,7 @@
"export": "Exportovať",
"selectOrExport": "Vybrať pre Export",
"toast": {
"success": "Export úspešne spustený. Súbor nájdete v adresári /exports.",
"success": "Export bol úspešne spustený. Súbor si pozrite na stránke exportov.",
"error": {
"failed": "Chyba spustenia exportu: {{error}}",
"endTimeMustAfterStartTime": "Čas konca musí byť po čase začiatku",
@ -117,6 +117,7 @@
"search": {
"placeholder": "Hľadať podľa štítku alebo podštítku..."
},
"noImages": "Pre tuto kameru sa nenašli žiadne miniatúry"
"noImages": "Pre tuto kameru sa nenašli žiadne miniatúry",
"unknownLabel": "Uložený obrázok spúšťača"
}
}

View File

@ -43,7 +43,8 @@
},
"train": {
"title": "Posledné klasifikácie",
"aria": "Vyberte Nedávne Klasifikácie"
"aria": "Vyberte Nedávne Klasifikácie",
"titleShort": "Nedávne"
},
"categories": "Triedy",
"createCategory": {

View File

@ -43,10 +43,17 @@
"trackedObject_one": "objekt",
"trackedObject_other": "objekty",
"noObjectDetailData": "Nie sú k dispozícii žiadne podrobné údaje o objekte.",
"label": "Detail"
"label": "Detail",
"settings": "Nastavenia podrobného zobrazenia",
"alwaysExpandActive": {
"title": "Rozbaľte vždy aktívne",
"desc": "Vždy rozbaľte podrobnosti objektu aktívnej položky recenzie, ak sú k dispozícii."
}
},
"objectTrack": {
"trackedPoint": "Sledovaný bod",
"clickToSeek": "Kliknutím prejdete na tento čas"
}
},
"zoomIn": "Priblížiť",
"zoomOut": "Oddialiť"
}

View File

@ -203,12 +203,16 @@
},
"hideObjectDetails": {
"label": "Skryť cestu objektu"
},
"viewTrackingDetails": {
"label": "Zobraziť podrobnosti sledovania",
"aria": "Zobraziť podrobnosti o sledovaní"
}
},
"dialog": {
"confirmDelete": {
"title": "Potvrdiť zmazanie",
"desc": "Odstránením tohto sledovaného objektu sa odstráni snímka, všetky uložené vloženia a všetky súvisiace položky životného cyklu objektu. Zaznamenaný záznam tohto sledovaného objektu v zobrazení História <em>NEBUDE</em> zmazaný.<br /><br />Naozaj chcete pokračovať?"
"desc": "Odstránením tohto sledovaného objektu sa odstráni snímka, všetky uložené vložené prvky a všetky súvisiace položky s podrobnosťami o sledovaní. Zaznamenané zábery tohto sledovaného objektu v zobrazení História <em>NEBUDÚ</em> odstránené.<br /><br />Naozaj chcete pokračovať?"
}
},
"noTrackedObjects": "Žiadne sledované objekty neboli nájdené",
@ -230,5 +234,53 @@
},
"concerns": {
"label": "Obavy"
},
"trackingDetails": {
"title": "Podrobnosti sledovania",
"noImageFound": "Pre túto časovú pečiatku sa nenašiel žiadny obrázok.",
"createObjectMask": "Vytvoriť masku objektu",
"adjustAnnotationSettings": "Upravte nastavenia anotácií",
"scrollViewTips": "Posúvaním zobrazíte významné momenty životného cyklu tohto objektu.",
"autoTrackingTips": "Pozície ohraničujúcich rámčekov budú pre kamery s automatickým sledovaním nepresné.",
"count": "{{first}} z {{second}}",
"trackedPoint": "Sledovaný bod",
"lifecycleItemDesc": {
"visible": "Zistený {{label}}",
"entered_zone": "{{label}} vstúpil do {{zones}}",
"active": "{{label}} sa stal aktívnym",
"stationary": "{{label}} sa zastavil",
"attribute": {
"faceOrLicense_plate": "Pre {{label}} bol zistený {{attribute}}",
"other": "{{label}} rozpoznané ako {{attribute}}"
},
"gone": "{{label}} zostalo",
"heard": "{{label}} počul",
"external": "Zistený {{label}}",
"header": {
"zones": "Zóny",
"ratio": "Pomer",
"area": "Oblasť"
}
},
"annotationSettings": {
"title": "Nastavenia anotácií",
"showAllZones": {
"title": "Zobraziť všetky zóny",
"desc": "Vždy zobrazovať zóny na rámoch, do ktorých objekty vstúpili."
},
"offset": {
"label": "Odsadenie anotácie",
"desc": "Tieto údaje pochádzajú z detektoru kamery, ale sú prepustené na obrázky z rekordného krmiva. Je nepravdepodobné, že dva prúdy sú perfektne synchronizované. V dôsledku toho, skreslenie box a zábery nebudú dokonale zaradiť. Toto nastavenie môžete použiť na ofsetovanie annotácií dopredu alebo dozadu, aby ste ich lepšie zladili s zaznamenanými zábermi.",
"millisecondsToOffset": "Milisekundy na posunutie detekcie anotácií. <em>Predvolené: 0</em>",
"tips": "TIP: Predstavte si klip udalosti, v ktorom osoba kráča zľava doprava. Ak je ohraničujúci rámček časovej osi udalosti stále naľavo od osoby, hodnota by sa mala znížiť. Podobne, ak osoba kráča zľava doprava a ohraničujúci rámček je stále pred ňou, hodnota by sa mala zvýšiť.",
"toast": {
"success": "Odsadenie anotácie pre {{camera}} bolo uložené do konfiguračného súboru. Reštartujte Frigate, aby sa zmeny prejavili."
}
}
},
"carousel": {
"previous": "Predchádzajúca snímka",
"next": "Ďalšia snímka"
}
}
}

View File

@ -13,5 +13,11 @@
"error": {
"renameExportFailed": "Nepodarilo sa premenovať export: {{errorMessage}}"
}
},
"tooltip": {
"shareExport": "Zdieľať export",
"downloadVideo": "Stiahnite si video",
"editName": "Upraviť meno",
"deleteExport": "Odstrániť export"
}
}

View File

@ -49,6 +49,10 @@
"playAlertVideos": {
"label": "Prehrať videá s upozornením",
"desc": "Predvolene sa nedávne upozornenia na paneli Živé vysielanie prehrávajú ako krátke cyklické videá. Túto možnosť vypnite, ak chcete zobrazovať iba statický obrázok nedávnych upozornení na tomto zariadení/prehliadači."
},
"displayCameraNames": {
"label": "Vždy Zobraziť názvy kamier",
"desc": "Vždy zobrazujte názvy kamier v čipe na ovládacom paneli živého náhľadu z viacerých kamier."
}
},
"storedLayouts": {
@ -292,7 +296,7 @@
"name": {
"title": "Meno",
"inputPlaceHolder": "Zadajte meno…",
"tips": "Názov musí mať aspoň 2 znaky a nesmie byť zhodný s názvom kamery alebo inej zóny."
"tips": "Názov musí mať aspoň 2 znaky, musí mať aspoň jedno písmeno a nesmie byť názvom kamery alebo inej zóny."
},
"inertia": {
"title": "Zotrvačnosť",
@ -793,5 +797,132 @@
}
}
}
},
"roles": {
"management": {
"title": "Správa roly diváka",
"desc": "Spravujte vlastné roly divákov a ich povolenia na prístup ku kamere pre túto inštanciu Frigate."
},
"addRole": "Pridať rolu",
"table": {
"role": "Rola",
"cameras": "Kamery",
"actions": "Akcie",
"noRoles": "Neboli nájdené žiadne vlastné role.",
"editCameras": "Editovať kamery",
"deleteRole": "Odstrániť rolu"
},
"toast": {
"success": {
"createRole": "Rola {{role}} bola úspešne vytvorená",
"updateCameras": "Kamery aktualizované pre rolu {{role}}",
"deleteRole": "Rola {{role}} bola úspešne odstránená",
"userRolesUpdated": "{{count}} užívateľ (y) priradené tejto úlohe boli aktualizované pre \"viewer\", ktorý má prístup ku všetkým kamerám."
},
"error": {
"createRoleFailed": "Nepodarilo sa vytvoriť rolu: {{errorMessage}}",
"updateCamerasFailed": "Nepodarilo sa aktualizovať kamery: {{errorMessage}}",
"deleteRoleFailed": "Nepodarilo sa odstrániť rolu: {{errorMessage}}",
"userUpdateFailed": "Nepodarilo sa aktualizovať používateľské role: {{errorMessage}}"
}
},
"dialog": {
"createRole": {
"title": "Vytvoriť novú rolu",
"desc": "Pridajte novú úlohu a zadajte prístup k kamerám."
},
"editCameras": {
"title": "Editovať Rolu Kamery",
"desc": "Aktualizujte prístup k kamere pre rolu <strong>{{role}}<strong>."
},
"deleteRole": {
"title": "Odstrániť rolu",
"desc": "Túto akciu nie je možné vrátiť späť. Týmto sa rola natrvalo odstráni a všetci používatelia s touto rolou budú priradení k role „pozerač“, ktorá umožní divákovi prístup ku všetkým kamerám.",
"warn": "Ste si istí, že chcete odstrániť <strong>{{role}}</strong>?",
"deleting": "Odstraňuje sa..."
},
"form": {
"role": {
"title": "Názov role",
"placeholder": "Zadajte názov roly",
"desc": "Povolené sú iba písmená, čísla, bodky a podčiarkovníky.",
"roleIsRequired": "Vyžaduje sa názov roly",
"roleOnlyInclude": "Názov role môže obsahovať iba písmená, čísla, . alebo _",
"roleExists": "Úloha s týmto menom už existuje."
},
"cameras": {
"title": "Kamery",
"desc": "Vyberte kamery, ku ktorým má táto rola prístup. Vyžaduje sa aspoň jedna kamera.",
"required": "Aspoň jedna kamera musí byť vybraná."
}
}
}
},
"notification": {
"title": "Notifikacie",
"notificationSettings": {
"title": "Nastavenia notifikácií",
"desc": "Frigate dokáže natívne odosielať push notifikácie do vášho zariadenia, keď je spustený v prehliadači alebo nainštalovaný ako PWA."
},
"notificationUnavailable": {
"title": "Notifikacie su nedostupné",
"desc": "Webové push notifikácie vyžadujú zabezpečený kontext (<code>https://…</code>). Ide o obmedzenie prehliadača. Ak chcete používať notifikácie, pristupujte k Frigate bezpečne."
},
"globalSettings": {
"title": "Globálne nastavenia",
"desc": "Dočasne pozastaviť upozornenia pre konkrétne kamery na všetkých registrovaných zariadeniach."
},
"email": {
"title": "E-mail",
"placeholder": "e.g. príklad@email.com",
"desc": "Vyžaduje sa platný e-mail, ktorý bude použitý na upozornenie v prípade akýchkoľvek problémov so službou push."
},
"cameras": {
"title": "Kamery",
"noCameras": "K dispozícii nie sú žiadne kamery",
"desc": "Vyberte, na ktoré kamery umožňujú notifikácie."
},
"deviceSpecific": "Špecifické nastavenia zariadenia",
"registerDevice": "Registrovať toto zariadenie",
"unregisterDevice": "Zrušte registráciu tohto zariadenia",
"sendTestNotification": "Odoslať testovacie oznámenie",
"unsavedRegistrations": "Neuložené registrácie oznámení",
"unsavedChanges": "Neuložené zmeny upozornení",
"active": "Upozornenia sú aktívne",
"suspended": "Oznámenie pozastavuju {{time}}",
"suspendTime": {
"suspend": "Pozastaviť",
"5minutes": "Pozastaviť na 5 minút",
"10minutes": "Pozastaviť na 10 minút",
"30minutes": "Pozastaviť na 30 minút",
"1hour": "Pozastaviť na 1 hodinu",
"12hours": "Pozastaviť na 12 hodín",
"24hours": "Pozastaviť na 24 hodín",
"untilRestart": "Pozastaviť do reštartovania"
},
"cancelSuspension": "Zrušiť pozastavenie",
"toast": {
"success": {
"registered": "Úspešne zaregistrované pre upozornenia. Pred odoslaním akýchkoľvek upozornení (vrátane testovacieho upozornenia) je potrebné reštartovať Frigate.",
"settingSaved": "Nastavenie oznámenia boli uložené."
},
"error": {
"registerFailed": "Uloženie registrácie upozornenia zlyhalo."
}
}
},
"frigatePlus": {
"title": "Nastavenie Frigate+",
"apiKey": {
"title": "Frigate + API kľúč",
"validated": "Frigate + API kľúč je detekovaný a overený",
"notValidated": "Frigate + API kľúč nie je detekovaný alebo nie je overený",
"desc": "Frigate+ API kľúč umožňuje integráciu s Frigate+ služby.",
"plusLink": "Prečítajte si viac o Frigate+"
},
"snapshotConfig": {
"title": "Konfigurácia snímky",
"desc": "Odosielanie do Frigate+ vyžaduje, aby boli v konfigurácii povolené snímky aj snímky <code>clean_copy</code>."
}
}
}

View File

@ -106,7 +106,7 @@
"enable": "Увімкнути",
"disabled": "Вимкнено",
"disable": "Вимкнути",
"save": "Зберігати",
"save": "Зберегти",
"download": "Завантажити",
"info": "Інфо",
"suspended": "Призупинено",
@ -238,7 +238,10 @@
}
},
"label": {
"back": "Повернутись"
"back": "Повернутись",
"hide": "Приховати {{item}}",
"show": "Показати {{item}}",
"ID": "ID"
},
"toast": {
"save": {
@ -282,5 +285,13 @@
"readTheDocumentation": "Прочитати документацію",
"information": {
"pixels": "{{area}}пикс"
},
"list": {
"two": "{{0}} і {{1}}",
"many": "{{items}}, і {{last}}"
},
"field": {
"optional": "Необов'язково",
"internalID": "Внутрішній ідентифікатор, який Frigate використовує в конфігурації та базі даних"
}
}

View File

@ -57,7 +57,7 @@
"endTimeMustAfterStartTime": "Час закінчення повинен бути після часу початку",
"noVaildTimeSelected": "Не вибрано допустимий діапазон часу"
},
"success": "Експорт успішно запущено. Файл доступний у теці /exports."
"success": "Експорт успішно розпочато. Перегляньте файл на сторінці експорту."
},
"fromTimeline": {
"saveExport": "Зберегти експорт",
@ -117,6 +117,7 @@
"search": {
"placeholder": "Пошук за міткою або підміткою..."
},
"noImages": "Для цієї камери не знайдено мініатюр"
"noImages": "Для цієї камери не знайдено мініатюр",
"unknownLabel": "Збережене зображення тригера"
}
}

View File

@ -1 +1,137 @@
{}
{
"documentTitle": "Моделі класифікації",
"button": {
"deleteClassificationAttempts": "Видалити зображення класифікації",
"renameCategory": "Перейменувати клас",
"deleteCategory": "Видалити клас",
"deleteImages": "Видалити зображення",
"trainModel": "Модель поїзда"
},
"toast": {
"success": {
"deletedCategory": "Видалений клас",
"deletedImage": "Видалені зображення",
"categorizedImage": "Зображення успішно класифіковано",
"trainedModel": "Успішно навчена модель.",
"trainingModel": "Успішно розпочато навчання моделі."
},
"error": {
"deleteImageFailed": "Не вдалося видалити: {{errorMessage}}",
"deleteCategoryFailed": "Не вдалося видалити клас: {{errorMessage}}",
"categorizeFailed": "Не вдалося класифікувати зображення: {{errorMessage}}",
"trainingFailed": "Не вдалося розпочати навчання моделі: {{errorMessage}}"
}
},
"deleteCategory": {
"title": "Видалити клас",
"desc": "Ви впевнені, що хочете видалити клас {{name}}? Це назавжди видалить усі пов'язані зображення та вимагатиме повторного навчання моделі."
},
"deleteDatasetImages": {
"title": "Видалити зображення набору даних",
"desc": "Ви впевнені, що хочете видалити {{count}} зображень з {{dataset}}? Цю дію неможливо скасувати, вона вимагатиме повторного навчання моделі."
},
"deleteTrainImages": {
"title": "Видалити зображення поїздів",
"desc": "Ви впевнені, що хочете видалити {{count}} зображень? Цю дію не можна скасувати."
},
"renameCategory": {
"title": "Перейменувати клас",
"desc": "Введіть нову назву для {{name}}. Вам потрібно буде перенавчити модель, щоб зміна назви набула чинності."
},
"description": {
"invalidName": "Недійсне ім'я. Ім'я може містити лише літери, цифри, пробіли, апострофи, символи підкреслення та дефіси."
},
"train": {
"title": "Нещодавні класифікації",
"titleShort": "Нещодавні",
"aria": "Виберіть останні класифікації"
},
"categories": "Заняття",
"createCategory": {
"new": "Створити новий клас"
},
"categorizeImageAs": "Класифікувати зображення як:",
"categorizeImage": "Класифікувати зображення",
"noModels": {
"object": {
"title": "Без моделей класифікації об'єктів",
"description": "Створіть власну модель для класифікації виявлених об'єктів.",
"buttonText": "Створення об'єктної моделі"
},
"state": {
"title": "Без моделей класифікації штатів",
"description": "Створіть власну модель для моніторингу та класифікації змін стану в певних областях камери.",
"buttonText": "Створити модель стану"
}
},
"wizard": {
"title": "Створити нову класифікацію",
"steps": {
"nameAndDefine": "Назва та визначення",
"stateArea": "Площа штату",
"chooseExamples": "Виберіть приклади"
},
"step1": {
"description": "Моделі станів відстежують зміни в зонах дії фіксованих камер (наприклад, відкриття/закриття дверей). Моделі об'єктів додають класифікації до виявлених об'єктів (наприклад, відомі тварини, кур'єри тощо).",
"name": "Ім'я",
"namePlaceholder": "Введіть назву моделі...",
"type": "Тип",
"typeState": "Штат",
"typeObject": "Об'єкт",
"objectLabel": "Мітка об'єкта",
"objectLabelPlaceholder": "Виберіть тип об'єкта...",
"classificationType": "Тип класифікації",
"classificationTypeTip": "Дізнайтеся про типи класифікації",
"classificationTypeDesc": "Підмітки додають додатковий текст до мітки об’єкта (наприклад, «Особа: UPS»). Атрибути це метадані для пошуку, що зберігаються окремо в метаданих об’єкта.",
"classificationSubLabel": "Підмітка",
"classificationAttribute": "Атрибут",
"classes": "Заняття",
"classesTip": "Дізнайтеся про заняття",
"classesStateDesc": "Визначте різні стани, в яких може перебувати зона вашої камери. Наприклад: «відкрито» та «закрито» для гаражних воріт.",
"classesObjectDesc": "Визначте різні категорії для класифікації виявлених об'єктів. Наприклад: «доставник», «мешканець», «незнайомець» для класифікації осіб.",
"classPlaceholder": "Введіть назву класу...",
"errors": {
"nameRequired": "Назва моделі обов'язкова",
"nameLength": "Назва моделі має містити не більше 64 символів",
"nameOnlyNumbers": "Назва моделі не може містити лише цифри",
"classRequired": "Потрібно хоча б 1 заняття",
"classesUnique": "Назви класів мають бути унікальними",
"stateRequiresTwoClasses": "Моделі станів вимагають щонайменше 2 класів",
"objectLabelRequired": "Будь ласка, виберіть мітку об'єкта",
"objectTypeRequired": "Будь ласка, виберіть тип класифікації"
},
"states": "Штати"
},
"step2": {
"description": "Виберіть камери та визначте область для моніторингу для кожної камери. Модель класифікуватиме стан цих областей.",
"cameras": "Камери",
"selectCamera": "Виберіть Камеру",
"noCameras": "Натисніть +, щоб додати камери",
"selectCameraPrompt": "Виберіть камеру зі списку, щоб визначити її зону спостереження"
},
"step3": {
"selectImagesPrompt": "Виберіть усі зображення з: {{className}}",
"selectImagesDescription": "Натисніть на зображення, щоб вибрати їх. Натисніть «Продовжити», коли закінчите з цим уроком.",
"generating": {
"title": "Створення зразків зображень",
"description": "Фрегат отримує типові зображення з ваших записів. Це може зайняти деякий час..."
},
"training": {
"title": "Модель навчання",
"description": "Ваша модель навчається у фоновому режимі. Закрийте це діалогове вікно, і ваша модель почне працювати, щойно навчання буде завершено."
},
"retryGenerate": "Генерація повторних спроб",
"noImages": "Немає згенерованих зразків зображень",
"classifying": "Класифікація та навчання...",
"trainingStarted": "Навчання розпочалося успішно",
"errors": {
"noCameras": "Немає налаштованих камер",
"noObjectLabel": "Мітку об'єкта не вибрано",
"generateFailed": "Не вдалося створити приклади: {{error}}",
"generationFailed": "Помилка генерації. Будь ласка, спробуйте ще раз.",
"classifyFailed": "Не вдалося класифікувати зображення: {{error}}"
},
"generateSuccess": "Зразки зображень успішно створено"
}
}
}

View File

@ -43,10 +43,17 @@
"trackedObject_one": "об'єкт",
"trackedObject_other": "об'єкти",
"noObjectDetailData": "Детальні дані про об'єкт недоступні.",
"label": "Деталь"
"label": "Деталь",
"settings": "Налаштування детального перегляду",
"alwaysExpandActive": {
"title": "Завжди розгортати активне",
"desc": "Завжди розгортайте деталі об'єкта активного елемента огляду, якщо вони доступні."
}
},
"objectTrack": {
"trackedPoint": "Відстежувана Точка",
"clickToSeek": "Натисніть, щоб перейти до цього часу"
}
},
"zoomIn": "Збільшити масштаб",
"zoomOut": "Зменшити масштаб"
}

View File

@ -168,7 +168,7 @@
"dialog": {
"confirmDelete": {
"title": "Підтвердити видалення",
"desc": "Видалення цього відстежуваного об’єкта призведе до видалення знімка, будь-яких збережених вбудованих елементів та будь-яких пов’язаних записів життєвого циклу об’єкта. Записані кадри цього відстежуваного обєкта в режимі перегляду історії <em>НЕ</em> будуть видалені.<br /><br />Ви впевнені, що хочете продовжити?"
"desc": "Видалення цього відстежуваного об'єкта призведе до видалення знімка, усіх збережених вбудованих даних та усіх пов'язаних записів деталей відстеження. Записані кадри цього відстежуваного об'єкта в режимі перегляду історії <em>НЕ</em> будуть видалені.<br /><br />Ви впевнені, що хочете продовжити?"
}
},
"itemMenu": {
@ -206,6 +206,16 @@
"audioTranscription": {
"label": "Транскрибувати",
"aria": "Запит на аудіотранскрипцію"
},
"viewTrackingDetails": {
"label": "Переглянути деталі відстеження",
"aria": "Показати деталі відстеження"
},
"showObjectDetails": {
"label": "Показати шлях до об'єкта"
},
"hideObjectDetails": {
"label": "Приховати шлях до об'єкта"
}
},
"noTrackedObjects": "Відстежуваних об'єктів не знайдено",
@ -224,5 +234,53 @@
},
"concerns": {
"label": "Проблеми"
},
"trackingDetails": {
"title": "Деталі відстеження",
"noImageFound": "Для цієї позначки часу не знайдено зображення.",
"createObjectMask": "Створити маску об'єкта",
"adjustAnnotationSettings": "Налаштування параметрів анотацій",
"scrollViewTips": "Прокрутіть, щоб переглянути важливі моменти життєвого циклу цього об'єкта.",
"autoTrackingTips": "Положення обмежувальних рамок будуть неточними для камер з автоматичним відстеженням.",
"count": "{{first}} з {{second}}",
"trackedPoint": "Відстежувана точка",
"lifecycleItemDesc": {
"visible": "Виявлено {{label}}",
"entered_zone": "{{label}} увійшов до {{zones}}",
"active": "{{label}} став активним",
"stationary": "{{label}} став нерухомим",
"attribute": {
"faceOrLicense_plate": "Виявлено атрибут {{attribute}} для {{label}}",
"other": "{{label}} розпізнано як {{attribute}}"
},
"gone": "{{label}} залишилося",
"heard": "{{label}} почув(ла)",
"external": "Виявлено {{label}}",
"header": {
"zones": "Зони",
"ratio": "Співвідношення",
"area": "Площа"
}
},
"annotationSettings": {
"title": "Налаштування анотацій",
"showAllZones": {
"title": "Показати всі зони",
"desc": "Завжди показувати зони на кадрах, де об'єкти увійшли в зону."
},
"offset": {
"label": "Зсув анотації",
"desc": "Ці дані надходять із каналу виявлення вашої камери, але накладаються на зображення з каналу запису. Малоймовірно, що ці два потоки будуть ідеально синхронізовані. Як результат, обмежувальна рамка та відеоматеріал не будуть ідеально збігатися. Ви можете використовувати це налаштування, щоб змістити анотації вперед або назад у часі, щоб краще узгодити їх із записаним відеоматеріалом.",
"millisecondsToOffset": "Мілісекунди для зміщення виявлених анотацій. <em>За замовчуванням: 0</em>",
"tips": "ПІДКАЗКА: Уявіть, що є кліп події, на якому людина йде зліва направо. Якщо обмежувальний прямокутник часової шкали події постійно знаходиться ліворуч від людини, то значення слід зменшити. Аналогічно, якщо людина йде зліва направо, а обмежувальний прямокутник постійно знаходиться попереду людини, то значення слід збільшити.",
"toast": {
"success": "Зміщення анотації для {{camera}} збережено у файлі конфігурації. Перезапустіть Frigate, щоб застосувати зміни."
}
}
},
"carousel": {
"previous": "Попередній слайд",
"next": "Наступний слайд"
}
}
}

View File

@ -13,5 +13,11 @@
"error": {
"renameExportFailed": "Не вдалося перейменувати експорт: {{errorMessage}}"
}
},
"tooltip": {
"shareExport": "Поділитися експортом",
"downloadVideo": "Завантажити відео",
"editName": "Редагувати ім'я",
"deleteExport": "Видалити експорт"
}
}

View File

@ -71,7 +71,7 @@
"trainFaceAs": "Тренуйте обличчя як:",
"trainFace": "Обличчя поїзда",
"description": {
"addFace": "Покрокові інструкції з додавання нової колекції до Бібліотеки облич.",
"addFace": "Додайте нову колекцію до Бібліотеки облич, завантаживши своє перше зображення.",
"placeholder": "Введіть назву для цієї колекції",
"invalidName": "Недійсне ім'я. Ім'я може містити лише літери, цифри, пробіли, апострофи, символи підкреслення та дефіси."
},

View File

@ -488,6 +488,10 @@
"playAlertVideos": {
"label": "Відтворити відео зі сповіщеннями",
"desc": "За замовчуванням останні сповіщення на панелі керування Live відтворюються як невеликі відеозаписи, що циклічно відтворюються. Вимкніть цю опцію, щоб відображати лише статичне зображення останніх сповіщень на цьому пристрої/у браузері."
},
"displayCameraNames": {
"label": "Завжди показувати назви камер",
"desc": "Завжди відображати назви камер у чіпі на панелі керування режимом живого перегляду з кількох камер."
}
},
"storedLayouts": {
@ -745,7 +749,7 @@
"triggers": {
"documentTitle": "Тригери",
"management": {
"title": "Управління тригерами",
"title": "Тригери",
"desc": "Керуйте тригерами для {{camera}}. Використовуйте тип мініатюри для спрацьовування на схожих мініатюрах до вибраного об’єкта відстеження, а тип опису для спрацьовування на схожих описах до вказаного вами тексту."
},
"addTrigger": "Додати Тригер",
@ -766,7 +770,9 @@
},
"actions": {
"alert": "Позначити як сповіщення",
"notification": "Надіслати сповіщення"
"notification": "Надіслати сповіщення",
"sub_label": "Додати підмітку",
"attribute": "Додати атрибут"
},
"dialog": {
"createTrigger": {
@ -784,25 +790,28 @@
"form": {
"name": {
"title": "Ім'я",
"placeholder": "Введіть назву тригера",
"placeholder": "Назвіть цей тригер",
"error": {
"minLength": "Ім'я має містити щонайменше 2 символи.",
"invalidCharacters": "Ім'я може містити лише літери, цифри, символи підкреслення та дефіси.",
"minLength": "Поле має містити щонайменше 2 символи.",
"invalidCharacters": "Поле може містити лише літери, цифри, символи підкреслення та дефіси.",
"alreadyExists": "Тригер із такою назвою вже існує для цієї камери."
}
},
"description": "Введіть унікальну назву або опис, щоб ідентифікувати цей тригер"
},
"enabled": {
"description": "Увімкнути або вимкнути цей тригер"
},
"type": {
"title": "Тип",
"placeholder": "Виберіть тип тригера"
"placeholder": "Виберіть тип тригера",
"description": "Спрацьовує, коли виявляється схожий опис відстежуваного об'єкта",
"thumbnail": "Спрацьовує, коли виявляється мініатюра схожого відстежуваного об'єкта"
},
"content": {
"title": "Зміст",
"imagePlaceholder": "Виберіть зображення",
"imagePlaceholder": "Виберіть мініатюру",
"textPlaceholder": "Введіть текстовий вміст",
"imageDesc": "Виберіть зображення, щоб запустити цю дію, коли буде виявлено схоже зображення.",
"imageDesc": "Відображаються лише 100 останніх мініатюр. Якщо ви не можете знайти потрібну мініатюру, перегляньте попередні об’єкти в розділі «Огляд» і налаштуйте тригер у меню.",
"textDesc": "Введіть текст, щоб запустити цю дію, коли буде виявлено схожий опис відстежуваного об’єкта.",
"error": {
"required": "Контент обов'язковий."
@ -813,11 +822,12 @@
"error": {
"min": "Поріг має бути щонайменше 0",
"max": "Поріг має бути не більше 1"
}
},
"desc": "Встановіть поріг подібності для цього тригера. Вищий поріг означає, що для спрацьовування тригера потрібна ближча відповідність."
},
"actions": {
"title": "Дії",
"desc": "За замовчуванням Frigate надсилає повідомлення MQTT для всіх тригерів. Виберіть додаткову дію, яку потрібно виконати, коли цей тригер спрацьовує.",
"desc": "За замовчуванням Frigate надсилає повідомлення MQTT для всіх тригерів. Підмітки додають назву тригера до мітки об'єкта. Атрибути це метадані, які можна шукати, що зберігаються окремо в метаданих відстежуваного об'єкта.",
"error": {
"min": "Потрібно вибрати принаймні одну дію."
}
@ -844,6 +854,23 @@
"semanticSearch": {
"title": "Семантичний пошук вимкнено",
"desc": "Для використання тригерів необхідно ввімкнути семантичний пошук."
},
"wizard": {
"title": "Створити тригер",
"step1": {
"description": "Налаштуйте основні параметри для вашого тригера."
},
"step2": {
"description": "Налаштуйте контент, який запускатиме цю дію."
},
"step3": {
"description": "Налаштуйте поріг та дії для цього тригера."
},
"steps": {
"nameAndType": "Ім'я та тип",
"configureData": "Налаштувати дані",
"thresholdAndActions": "Поріг та дії"
}
}
},
"roles": {

View File

@ -44,10 +44,17 @@
"trackedObject_one": "目标或物体",
"trackedObject_other": "目标或物体",
"noObjectDetailData": "没有目标详细信息。",
"label": "详细信息"
"label": "详细信息",
"settings": "详细视图设置",
"alwaysExpandActive": {
"title": "始终展开当前项",
"desc": "在可用情况下,将始终展开当前核查项的对象详细信息。"
}
},
"objectTrack": {
"trackedPoint": "追踪点",
"clickToSeek": "点击从该时间进行寻找"
}
},
"zoomIn": "放大",
"zoomOut": "缩小"
}

View File

@ -266,7 +266,7 @@
},
"offset": {
"label": "标记偏移量",
"desc": "此数据来自摄像头的检测视频流,但叠加在录制视频流的图像上。由于两个视频流通常不会完全同步,因此边框与画面可能无法完全对齐。不过,可以使用 <code>annotation_offset</code> 字段进行调整。",
"desc": "此数据来自摄像头的检测视频流,但叠加在录制视频流的画面上。两个视频流可能不会完全同步,因此边框与画面可能无法完全对齐。可以使用此设置将标记在时间轴上向前或向后偏移,以更好地与录制画面对齐。",
"millisecondsToOffset": "用于偏移检测标记的毫秒数。<em> 默认值0</em>",
"tips": "提示:假设有一段人从左向右走的事件录制,如果事件时间轴中的边框始终在人的左侧(即后方),则应该减小偏移值;反之,如果边框始终领先于人物,则应增大偏移值。",
"toast": {

View File

@ -51,6 +51,10 @@
"playAlertVideos": {
"label": "播放警报视频",
"desc": "默认情况下,实时监控页面上的最新警报会以一小段循环视频的形式进行播放。禁用此选项将仅显示浏览器本地缓存的静态图片。"
},
"displayCameraNames": {
"label": "始终显示摄像头名称",
"desc": "在有多摄像头情况下的实时监控页面,将始终显示摄像头名称标签。"
}
},
"storedLayouts": {
@ -761,7 +765,9 @@
},
"actions": {
"alert": "标记为警报",
"notification": "发送通知"
"notification": "发送通知",
"sub_label": "添加子标签",
"attribute": "添加属性"
},
"dialog": {
"createTrigger": {
@ -816,7 +822,7 @@
},
"actions": {
"title": "动作",
"desc": "默认情况下Frigate 会为所有触发器发送 MQTT 消息。请选择此触发器触发时需要执行的附加操作。",
"desc": "默认情况下Frigate 会为所有触发器发送 MQTT 消息。子标签会将触发器名称添加到对象标签中。属性是可搜索的元数据,独立存储在追踪对象的元数据中。",
"error": {
"min": "必须至少选择一项动作。"
}

View File

@ -43,7 +43,8 @@
},
"train": {
"title": "最近分类记录",
"aria": "选择最近分类记录"
"aria": "选择最近分类记录",
"titleShort": "最近"
},
"categories": "类别",
"createCategory": {

View File

@ -394,7 +394,9 @@ export default function Step1NameAndDefine({
<div className="flex items-center justify-between">
<div className="flex items-center gap-1">
<FormLabel className="text-primary-variant">
{t("wizard.step1.classes")}
{watchedModelType === "state"
? t("wizard.step1.states")
: t("wizard.step1.classes")}
</FormLabel>
<Popover>
<PopoverTrigger asChild>

View File

@ -13,6 +13,9 @@ import { cn } from "@/lib/utils";
import { useTranslation } from "react-i18next";
import { Event } from "@/types/event";
// Use a small tolerance (10ms) for browsers with seek precision by-design issues
const TOLERANCE = 0.01;
type ObjectTrackOverlayProps = {
camera: string;
showBoundingBoxes?: boolean;
@ -166,41 +169,45 @@ export default function ObjectTrackOverlay({
}) || [];
// show full path once current time has reached the object's start time
const combinedPoints = [...savedPathPoints, ...eventSequencePoints]
.sort((a, b) => a.timestamp - b.timestamp)
.filter(
(point) =>
currentTime >= (eventData?.start_time ?? 0) &&
point.timestamp >= (eventData?.start_time ?? 0) &&
point.timestamp <= (eventData?.end_time ?? Infinity),
);
// event.start_time is in DETECT stream time, so convert it to record stream time for comparison
const eventStartTimeRecord =
(eventData?.start_time ?? 0) + annotationOffset / 1000;
const allPoints = [...savedPathPoints, ...eventSequencePoints].sort(
(a, b) => a.timestamp - b.timestamp,
);
const combinedPoints = allPoints.filter(
(point) =>
currentTime >= eventStartTimeRecord - TOLERANCE &&
point.timestamp <= effectiveCurrentTime + TOLERANCE,
);
// Get color for this object
const label = eventData?.label || "unknown";
const color = getObjectColor(label, objectId);
// Get current zones
// zones (with tolerance for browsers with seek precision by-design issues)
const currentZones =
timelineData
?.filter(
(event: TrackingDetailsSequence) =>
event.timestamp <= effectiveCurrentTime,
event.timestamp <= effectiveCurrentTime + TOLERANCE,
)
.sort(
(a: TrackingDetailsSequence, b: TrackingDetailsSequence) =>
b.timestamp - a.timestamp,
)[0]?.data?.zones || [];
// Get current bounding box
const currentBox = timelineData
?.filter(
(event: TrackingDetailsSequence) =>
event.timestamp <= effectiveCurrentTime && event.data.box,
)
.sort(
(a: TrackingDetailsSequence, b: TrackingDetailsSequence) =>
b.timestamp - a.timestamp,
)[0]?.data?.box;
// bounding box (with tolerance for browsers with seek precision by-design issues)
const boxCandidates = timelineData?.filter(
(event: TrackingDetailsSequence) =>
event.timestamp <= effectiveCurrentTime + TOLERANCE &&
event.data.box,
);
const currentBox = boxCandidates?.sort(
(a: TrackingDetailsSequence, b: TrackingDetailsSequence) =>
b.timestamp - a.timestamp,
)[0]?.data?.box;
return {
objectId,
@ -221,6 +228,7 @@ export default function ObjectTrackOverlay({
getObjectColor,
config,
camera,
annotationOffset,
]);
// Collect all zones across all objects
@ -274,9 +282,10 @@ export default function ObjectTrackOverlay({
const handlePointClick = useCallback(
(timestamp: number) => {
onSeekToTime?.(timestamp, false);
// Convert detect stream timestamp to record stream timestamp before seeking
onSeekToTime?.(timestamp + annotationOffset / 1000, false);
},
[onSeekToTime],
[onSeekToTime, annotationOffset],
);
const zonePolygons = useMemo(() => {

View File

@ -91,8 +91,8 @@ export default function AnnotationOffsetSlider({ className }: Props) {
<div className="w-full flex-1 landscape:flex">
<Slider
value={[annotationOffset]}
min={-1500}
max={1500}
min={-2500}
max={2500}
step={50}
onValueChange={handleChange}
/>

View File

@ -1,577 +0,0 @@
import { isDesktop, isIOS, isMobile } from "react-device-detect";
import {
Sheet,
SheetContent,
SheetDescription,
SheetHeader,
SheetTitle,
} from "../../ui/sheet";
import useSWR from "swr";
import { FrigateConfig } from "@/types/frigateConfig";
import { useFormattedTimestamp } from "@/hooks/use-date-utils";
import { getIconForLabel } from "@/utils/iconUtil";
import { useApiHost } from "@/api";
import {
ReviewDetailPaneType,
ReviewSegment,
ThreatLevel,
} from "@/types/review";
import { Event } from "@/types/event";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { cn } from "@/lib/utils";
import { FrigatePlusDialog } from "../dialog/FrigatePlusDialog";
import TrackingDetails from "./TrackingDetails";
import Chip from "@/components/indicators/Chip";
import { FaDownload, FaImages, FaShareAlt } from "react-icons/fa";
import FrigatePlusIcon from "@/components/icons/FrigatePlusIcon";
import { FaArrowsRotate } from "react-icons/fa6";
import {
Tooltip,
TooltipContent,
TooltipTrigger,
} from "@/components/ui/tooltip";
import { useNavigate } from "react-router-dom";
import { Button } from "@/components/ui/button";
import { baseUrl } from "@/api/baseUrl";
import { shareOrCopy } from "@/utils/browserUtil";
import {
MobilePage,
MobilePageContent,
MobilePageDescription,
MobilePageHeader,
MobilePageTitle,
} from "@/components/mobile/MobilePage";
import { DownloadVideoButton } from "@/components/button/DownloadVideoButton";
import { TooltipPortal } from "@radix-ui/react-tooltip";
import { LuSearch } from "react-icons/lu";
import useKeyboardListener from "@/hooks/use-keyboard-listener";
import { Trans, useTranslation } from "react-i18next";
import { getTranslatedLabel } from "@/utils/i18n";
import { CameraNameLabel } from "@/components/camera/CameraNameLabel";
type ReviewDetailDialogProps = {
review?: ReviewSegment;
setReview: (review: ReviewSegment | undefined) => void;
};
export default function ReviewDetailDialog({
review,
setReview,
}: ReviewDetailDialogProps) {
const { t } = useTranslation(["views/explore"]);
const { data: config } = useSWR<FrigateConfig>("config", {
revalidateOnFocus: false,
});
const navigate = useNavigate();
// upload
const [upload, setUpload] = useState<Event>();
// data
const { data: events } = useSWR<Event[]>(
review ? ["event_ids", { ids: review.data.detections.join(",") }] : null,
);
const aiAnalysis = useMemo(() => review?.data?.metadata, [review]);
const aiThreatLevel = useMemo(() => {
if (
!aiAnalysis ||
(!aiAnalysis.potential_threat_level && !aiAnalysis.other_concerns)
) {
return "None";
}
let concerns = "";
switch (aiAnalysis.potential_threat_level) {
case ThreatLevel.SUSPICIOUS:
concerns = `${t("suspiciousActivity", { ns: "views/events" })}\n`;
break;
case ThreatLevel.DANGER:
concerns = `${t("threateningActivity", { ns: "views/events" })}\n`;
break;
}
(aiAnalysis.other_concerns ?? []).forEach((c) => {
concerns += `${c}\n`;
});
return concerns || "None";
}, [aiAnalysis, t]);
const hasMismatch = useMemo(() => {
if (!review || !events) {
return false;
}
return events.length != review?.data.detections.length;
}, [review, events]);
const missingObjects = useMemo(() => {
if (!review || !events) {
return [];
}
const detectedIds = review.data.detections;
const missing = Array.from(
new Set(
events
.filter((event) => !detectedIds.includes(event.id))
.map((event) => event.label),
),
);
return missing;
}, [review, events]);
const formattedDate = useFormattedTimestamp(
review?.start_time ?? 0,
config?.ui.time_format == "24hour"
? t("time.formattedTimestampMonthDayYearHourMinute.24hour", {
ns: "common",
})
: t("time.formattedTimestampMonthDayYearHourMinute.12hour", {
ns: "common",
}),
config?.ui.timezone,
);
// content
const [selectedEvent, setSelectedEvent] = useState<Event>();
const [pane, setPane] = useState<ReviewDetailPaneType>("overview");
// dialog and mobile page
const [isOpen, setIsOpen] = useState(review != undefined);
const handleOpenChange = useCallback(
(open: boolean) => {
setIsOpen(open);
if (!open) {
// short timeout to allow the mobile page animation
// to complete before updating the state
setTimeout(() => {
setReview(undefined);
setSelectedEvent(undefined);
setPane("overview");
}, 300);
}
},
[setReview, setIsOpen],
);
useEffect(() => {
setIsOpen(review != undefined);
// we know that these deps are correct
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [review]);
// keyboard listener
useKeyboardListener(["Esc"], (key, modifiers) => {
if (key == "Esc" && modifiers.down && !modifiers.repeat) {
setIsOpen(false);
}
return true;
});
const Overlay = isDesktop ? Sheet : MobilePage;
const Content = isDesktop ? SheetContent : MobilePageContent;
const Header = isDesktop ? SheetHeader : MobilePageHeader;
const Title = isDesktop ? SheetTitle : MobilePageTitle;
const Description = isDesktop ? SheetDescription : MobilePageDescription;
if (!review) {
return;
}
return (
<>
<Overlay
open={isOpen ?? false}
onOpenChange={handleOpenChange}
enableHistoryBack={true}
>
<FrigatePlusDialog
upload={upload}
onClose={() => setUpload(undefined)}
onEventUploaded={() => {
if (upload) {
upload.plus_id = "new_upload";
}
}}
/>
<Content
className={cn(
"scrollbar-container overflow-y-auto",
isDesktop && pane == "overview"
? "sm:max-w-xl"
: "pt-2 sm:max-w-4xl",
isMobile && "px-4",
)}
>
<span tabIndex={0} className="sr-only" />
{pane == "overview" && (
<Header className="justify-center">
<Title>{t("details.item.title")}</Title>
<Description className="sr-only">
{t("details.item.desc")}
</Description>
<div
className={cn(
"absolute flex gap-2 lg:flex-col",
isDesktop && "right-1 top-8",
isMobile && "right-0 top-3",
)}
>
<Tooltip>
<TooltipTrigger asChild>
<Button
aria-label={t("details.item.button.share")}
size="sm"
onClick={() =>
shareOrCopy(`${baseUrl}review?id=${review.id}`)
}
>
<FaShareAlt className="size-4 text-secondary-foreground" />
</Button>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{t("details.item.button.share")}
</TooltipContent>
</TooltipPortal>
</Tooltip>
<Tooltip>
<TooltipTrigger>
<DownloadVideoButton
source={`${baseUrl}api/${review.camera}/start/${review.start_time}/end/${review.end_time || Date.now() / 1000}/clip.mp4`}
camera={review.camera}
startTime={review.start_time}
/>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{t("button.download", { ns: "common" })}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
</Header>
)}
{pane == "overview" && (
<div className="flex flex-col gap-5 md:mt-3">
{aiAnalysis != undefined && (
<div
className={cn(
"flex h-full w-full flex-col gap-2 rounded-md bg-card p-2",
isDesktop && "m-2 w-[90%]",
)}
>
{t("aiAnalysis.title")}
<div className="text-sm text-primary/40">
{t("details.description.label")}
</div>
<div className="text-sm">{aiAnalysis.scene}</div>
<div className="text-sm text-primary/40">
{t("details.score.label")}
</div>
<div className="text-sm">{aiAnalysis.confidence * 100}%</div>
<div className="text-sm text-primary/40">
{t("concerns.label")}
</div>
<div className="text-sm">{aiThreatLevel}</div>
</div>
)}
<div className="flex w-full flex-row">
<div className="flex w-full flex-col gap-3">
<div className="flex flex-col gap-1.5">
<div className="text-sm text-primary/40">
{t("details.camera")}
</div>
<div className="text-sm smart-capitalize">
<CameraNameLabel camera={review.camera} />
</div>
</div>
<div className="flex flex-col gap-1.5">
<div className="text-sm text-primary/40">
{t("details.timestamp")}
</div>
<div className="text-sm">{formattedDate}</div>
</div>
</div>
<div className="flex w-full flex-col items-center gap-2">
<div className="flex w-full flex-col gap-1.5 lg:pr-8">
<div className="text-sm text-primary/40">
{t("details.objects")}
</div>
<div className="scrollbar-container flex max-h-32 flex-col items-start gap-2 overflow-y-auto text-sm smart-capitalize">
{events?.map((event) => {
return (
<div
key={event.id}
className="flex flex-row items-center gap-2 smart-capitalize"
>
{getIconForLabel(
event.label,
"size-3 text-primary",
)}
{event.sub_label ??
event.label.replaceAll("_", " ")}{" "}
({Math.round(event.data.top_score * 100)}%)
<Tooltip>
<TooltipTrigger>
<div
className="cursor-pointer"
onClick={() => {
navigate(`/explore?event_id=${event.id}`);
}}
>
<LuSearch className="size-4 text-muted-foreground" />
</div>
</TooltipTrigger>
<TooltipPortal>
<TooltipContent>
{t("details.item.button.viewInExplore")}
</TooltipContent>
</TooltipPortal>
</Tooltip>
</div>
);
})}
</div>
</div>
{review.data.zones.length > 0 && (
<div className="scrollbar-container flex max-h-32 w-full flex-col gap-1.5">
<div className="text-sm text-primary/40">
{t("details.zones")}
</div>
<div className="flex flex-col items-start gap-2 text-sm smart-capitalize">
{review.data.zones.map((zone) => {
return (
<div
key={zone}
className="flex flex-row items-center gap-2 smart-capitalize"
>
{zone.replaceAll("_", " ")}
</div>
);
})}
</div>
</div>
)}
</div>
</div>
{hasMismatch && (
<div className="p-4 text-center text-sm">
{(() => {
const detectedCount = Math.abs(
(events?.length ?? 0) -
(review?.data.detections.length ?? 0),
);
return t("details.item.tips.mismatch", {
count: detectedCount,
});
})()}
{missingObjects.length > 0 && (
<div className="mt-2">
<Trans
ns="views/explore"
values={{
objects: missingObjects
.map((x) => getTranslatedLabel(x))
.join(", "),
}}
>
details.item.tips.hasMissingObjects
</Trans>
</div>
)}
</div>
)}
<div className="relative flex size-full flex-col gap-2">
{events?.map((event) => (
<EventItem
key={event.id}
event={event}
setPane={setPane}
setSelectedEvent={setSelectedEvent}
setUpload={setUpload}
/>
))}
</div>
</div>
)}
{pane == "details" && selectedEvent && (
<div className="mt-0 flex size-full flex-col gap-2">
<TrackingDetails event={selectedEvent} setPane={setPane} />
</div>
)}
</Content>
</Overlay>
</>
);
}
type EventItemProps = {
event: Event;
setPane: React.Dispatch<React.SetStateAction<ReviewDetailPaneType>>;
setSelectedEvent: React.Dispatch<React.SetStateAction<Event | undefined>>;
setUpload?: React.Dispatch<React.SetStateAction<Event | undefined>>;
};
function EventItem({
event,
setPane,
setSelectedEvent,
setUpload,
}: EventItemProps) {
const { t } = useTranslation(["views/explore"]);
const { data: config } = useSWR<FrigateConfig>("config", {
revalidateOnFocus: false,
});
const apiHost = useApiHost();
const imgRef = useRef(null);
const [hovered, setHovered] = useState(isMobile);
const navigate = useNavigate();
return (
<>
<div
className={cn(
"relative mr-auto",
!event.has_snapshot && "flex flex-row items-center justify-center",
)}
onMouseEnter={isDesktop ? () => setHovered(true) : undefined}
onMouseLeave={isDesktop ? () => setHovered(false) : undefined}
key={event.id}
>
{event.has_snapshot && (
<>
<div className="pointer-events-none absolute inset-x-0 top-0 h-[30%] w-full rounded-lg bg-gradient-to-b from-black/20 to-transparent md:rounded-2xl"></div>
<div className="pointer-events-none absolute inset-x-0 bottom-0 z-10 h-[10%] w-full rounded-lg bg-gradient-to-t from-black/20 to-transparent md:rounded-2xl"></div>
</>
)}
<img
ref={imgRef}
className={cn(
"select-none rounded-lg object-contain transition-opacity",
)}
style={
isIOS
? {
WebkitUserSelect: "none",
WebkitTouchCallout: "none",
}
: undefined
}
draggable={false}
src={
event.has_snapshot
? `${apiHost}api/events/${event.id}/snapshot.jpg`
: `${apiHost}api/events/${event.id}/thumbnail.webp`
}
/>
{hovered && (
<div>
<div
className={cn("absolute right-1 top-1 flex items-center gap-2")}
>
<Tooltip>
<TooltipTrigger asChild>
<a
download
href={
event.has_snapshot
? `${apiHost}api/events/${event.id}/snapshot.jpg`
: `${apiHost}api/events/${event.id}/thumbnail.webp`
}
>
<Chip className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500">
<FaDownload className="size-4 text-white" />
</Chip>
</a>
</TooltipTrigger>
<TooltipContent>
{t("button.download", { ns: "common" })}
</TooltipContent>
</Tooltip>
{event.has_snapshot &&
event.plus_id == undefined &&
event.data.type == "object" &&
config?.plus.enabled && (
<Tooltip>
<TooltipTrigger>
<Chip
className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500"
onClick={() => {
setUpload?.(event);
}}
>
<FrigatePlusIcon className="size-4 text-white" />
</Chip>
</TooltipTrigger>
<TooltipContent>
{t("itemMenu.submitToPlus.label")}
</TooltipContent>
</Tooltip>
)}
{event.has_clip && (
<Tooltip>
<TooltipTrigger>
<Chip
className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500"
onClick={() => {
setPane("details");
setSelectedEvent(event);
}}
>
<FaArrowsRotate className="size-4 text-white" />
</Chip>
</TooltipTrigger>
<TooltipContent>
{t("itemMenu.viewTrackingDetails.label")}
</TooltipContent>
</Tooltip>
)}
{event.has_snapshot && config?.semantic_search.enabled && (
<Tooltip>
<TooltipTrigger>
<Chip
className="cursor-pointer rounded-md bg-gray-500 bg-gradient-to-br from-gray-400 to-gray-500"
onClick={() => {
navigate(
`/explore?search_type=similarity&event_id=${event.id}`,
);
}}
>
<FaImages className="size-4 text-white" />
</Chip>
</TooltipTrigger>
<TooltipContent>
{t("itemMenu.findSimilar.label")}
</TooltipContent>
</Tooltip>
)}
</div>
</div>
)}
</div>
</>
);
}

View File

@ -31,10 +31,9 @@ import {
FaDownload,
FaHistory,
FaImage,
FaRegListAlt,
FaVideo,
} from "react-icons/fa";
import TrackingDetails from "./TrackingDetails";
import { TrackingDetails } from "./TrackingDetails";
import { DetailStreamProvider } from "@/context/detail-stream-context";
import {
MobilePage,
MobilePageContent,
@ -80,13 +79,9 @@ import { getTranslatedLabel } from "@/utils/i18n";
import { CgTranscript } from "react-icons/cg";
import { CameraNameLabel } from "@/components/camera/CameraNameLabel";
import { PiPath } from "react-icons/pi";
import Heading from "@/components/ui/heading";
const SEARCH_TABS = [
"details",
"snapshot",
"video",
"tracking_details",
] as const;
const SEARCH_TABS = ["snapshot", "tracking_details"] as const;
export type SearchTab = (typeof SEARCH_TABS)[number];
type SearchDetailDialogProps = {
@ -109,6 +104,7 @@ export default function SearchDetailDialog({
const { data: config } = useSWR<FrigateConfig>("config", {
revalidateOnFocus: false,
});
const apiHost = useApiHost();
// tabs
@ -149,16 +145,6 @@ export default function SearchDetailDialog({
const views = [...SEARCH_TABS];
if (!search.has_snapshot) {
const index = views.indexOf("snapshot");
views.splice(index, 1);
}
if (!search.has_clip) {
const index = views.indexOf("video");
views.splice(index, 1);
}
if (search.data.type != "object" || !search.has_clip) {
const index = views.indexOf("tracking_details");
views.splice(index, 1);
@ -173,10 +159,50 @@ export default function SearchDetailDialog({
}
if (!searchTabs.includes(pageToggle)) {
setSearchPage("details");
setSearchPage("snapshot");
}
}, [pageToggle, searchTabs, setSearchPage]);
// Tabs component for reuse
const tabsComponent = (
<ScrollArea className="w-full whitespace-nowrap">
<div className="flex flex-row">
<ToggleGroup
className="*:rounded-md *:px-3 *:py-4"
type="single"
size="sm"
value={pageToggle}
onValueChange={(value: SearchTab) => {
if (value) {
setPageToggle(value);
}
}}
>
{Object.values(searchTabs).map((item) => (
<ToggleGroupItem
key={item}
className={`flex scroll-mx-10 items-center justify-between gap-2 ${pageToggle == item ? "" : "*:text-muted-foreground"}`}
value={item}
data-nav-item={item}
aria-label={`Select ${item}`}
>
{item == "snapshot" && <FaImage className="size-4" />}
{item == "tracking_details" && <PiPath className="size-4" />}
<div className="smart-capitalize">
{item === "snapshot"
? search?.has_snapshot
? t("type.snapshot")
: t("type.thumbnail")
: t(`type.${item}`)}
</div>
</ToggleGroupItem>
))}
</ToggleGroup>
<ScrollBar orientation="horizontal" className="h-0" />
</div>
</ScrollArea>
);
if (!search) {
return;
}
@ -190,92 +216,188 @@ export default function SearchDetailDialog({
const Description = isDesktop ? DialogDescription : MobilePageDescription;
return (
<Overlay
open={isOpen}
onOpenChange={handleOpenChange}
enableHistoryBack={true}
<DetailStreamProvider
isDetailMode={true}
currentTime={(search as unknown as Event)?.start_time ?? 0}
camera={(search as unknown as Event)?.camera ?? ""}
initialSelectedObjectIds={[(search as unknown as Event).id as string]}
>
<Content
className={cn(
"scrollbar-container overflow-y-auto",
isDesktop &&
"max-h-[95dvh] sm:max-w-xl md:max-w-4xl lg:max-w-4xl xl:max-w-7xl",
isMobile && "px-4",
)}
<Overlay
open={isOpen}
onOpenChange={handleOpenChange}
enableHistoryBack={true}
>
<Header>
<Title>{t("trackedObjectDetails")}</Title>
<Description className="sr-only">
{t("trackedObjectDetails")}
</Description>
</Header>
<ScrollArea
className={cn("w-full whitespace-nowrap", isMobile && "my-2")}
<Content
className={cn(
"scrollbar-container overflow-y-auto",
isDesktop &&
"max-h-[95dvh] sm:max-w-xl md:max-w-4xl lg:max-w-4xl xl:max-w-7xl",
isDesktop &&
page == "tracking_details" &&
"lg:max-w-[75%] xl:max-w-[80%]",
isMobile && "px-4",
)}
>
<div className="flex flex-row">
<ToggleGroup
className="*:rounded-md *:px-3 *:py-4"
type="single"
size="sm"
value={pageToggle}
onValueChange={(value: SearchTab) => {
if (value) {
setPageToggle(value);
}
}}
>
{Object.values(searchTabs).map((item) => (
<ToggleGroupItem
key={item}
className={`flex scroll-mx-10 items-center justify-between gap-2 ${page == "details" ? "last:mr-20" : ""} ${pageToggle == item ? "" : "*:text-muted-foreground"}`}
value={item}
data-nav-item={item}
aria-label={`Select ${item}`}
<Header>
<Title>{t("trackedObjectDetails")}</Title>
<Description className="sr-only">
{t("trackedObjectDetails")}
</Description>
</Header>
{isDesktop ? (
page === "tracking_details" ? (
<TrackingDetails
className="size-full"
event={search as unknown as Event}
tabs={tabsComponent}
/>
) : (
<div className="flex h-full gap-4 overflow-hidden">
<div
className={cn(
"scrollbar-container flex-[3] overflow-y-hidden",
page === "snapshot" && !search.has_snapshot && "flex-[2]",
)}
>
{item == "details" && <FaRegListAlt className="size-4" />}
{item == "snapshot" && <FaImage className="size-4" />}
{item == "video" && <FaVideo className="size-4" />}
{item == "tracking_details" && <PiPath className="size-4" />}
<div className="smart-capitalize">{t(`type.${item}`)}</div>
</ToggleGroupItem>
))}
</ToggleGroup>
<ScrollBar orientation="horizontal" className="h-0" />
</div>
</ScrollArea>
{page == "details" && (
<ObjectDetailsTab
search={search}
config={config}
setSearch={setSearch}
setSimilarity={setSimilarity}
setInputFocused={setInputFocused}
/>
)}
{page == "snapshot" && (
<ObjectSnapshotTab
search={
{
...search,
plus_id: config?.plus?.enabled ? search.plus_id : "not_enabled",
} as unknown as Event
}
onEventUploaded={() => {
search.plus_id = "new_upload";
}}
/>
)}
{page == "video" && <VideoTab search={search} />}
{page == "tracking_details" && (
<TrackingDetails
className="w-full overflow-x-hidden"
event={search as unknown as Event}
fullscreen={true}
setPane={() => {}}
/>
)}
</Content>
</Overlay>
{page === "snapshot" && search.has_snapshot && (
<ObjectSnapshotTab
search={
{
...search,
plus_id: config?.plus?.enabled
? search.plus_id
: "not_enabled",
} as unknown as Event
}
onEventUploaded={() => {
search.plus_id = "new_upload";
}}
/>
)}
{page === "snapshot" && !search.has_snapshot && (
<img
className="size-full select-none rounded-lg object-contain transition-opacity"
style={
isIOS
? {
WebkitUserSelect: "none",
WebkitTouchCallout: "none",
}
: undefined
}
draggable={false}
src={`${apiHost}api/events/${search.id}/thumbnail.webp`}
/>
)}
</div>
<div className="flex flex-[2] flex-col gap-4 overflow-hidden">
{tabsComponent}
<div className="scrollbar-container flex-1 overflow-y-auto">
{page == "snapshot" && (
<ObjectDetailsTab
search={search}
config={config}
setSearch={setSearch}
setSimilarity={setSimilarity}
setInputFocused={setInputFocused}
showThumbnail={false}
/>
)}
</div>
</div>
</div>
)
) : (
<>
<ScrollArea
className={cn("w-full whitespace-nowrap", isMobile && "my-2")}
>
<div className="flex flex-row">
<ToggleGroup
className="*:rounded-md *:px-3 *:py-4"
type="single"
size="sm"
value={pageToggle}
onValueChange={(value: SearchTab) => {
if (value) {
setPageToggle(value);
}
}}
>
{Object.values(searchTabs).map((item) => (
<ToggleGroupItem
key={item}
className={`flex scroll-mx-10 items-center justify-between gap-2 ${pageToggle == item ? "" : "*:text-muted-foreground"}`}
value={item}
data-nav-item={item}
aria-label={`Select ${item}`}
>
{item == "snapshot" && <FaImage className="size-4" />}
{item == "tracking_details" && (
<PiPath className="size-4" />
)}
<div className="smart-capitalize">
{t(`type.${item}`)}
</div>
</ToggleGroupItem>
))}
</ToggleGroup>
<ScrollBar orientation="horizontal" className="h-0" />
</div>
</ScrollArea>
{page == "snapshot" && (
<>
{search.has_snapshot && (
<ObjectSnapshotTab
search={
{
...search,
plus_id: config?.plus?.enabled
? search.plus_id
: "not_enabled",
} as unknown as Event
}
onEventUploaded={() => {
search.plus_id = "new_upload";
}}
/>
)}
{page == "snapshot" && !search.has_snapshot && (
<img
className="w-full select-none rounded-lg object-contain transition-opacity"
style={
isIOS
? {
WebkitUserSelect: "none",
WebkitTouchCallout: "none",
}
: undefined
}
draggable={false}
src={`${apiHost}api/events/${search.id}/thumbnail.webp`}
/>
)}
<Heading as="h3" className="mt-2 smart-capitalize">
{t("type.details")}
</Heading>
<ObjectDetailsTab
search={search}
config={config}
setSearch={setSearch}
setSimilarity={setSimilarity}
setInputFocused={setInputFocused}
showThumbnail={false}
/>
</>
)}
{page == "tracking_details" && (
<TrackingDetails event={search as unknown as Event} />
)}
</>
)}
</Content>
</Overlay>
</DetailStreamProvider>
);
}
@ -285,6 +407,7 @@ type ObjectDetailsTabProps = {
setSearch: (search: SearchResult | undefined) => void;
setSimilarity?: () => void;
setInputFocused: React.Dispatch<React.SetStateAction<boolean>>;
showThumbnail?: boolean;
};
function ObjectDetailsTab({
search,
@ -292,6 +415,7 @@ function ObjectDetailsTab({
setSearch,
setSimilarity,
setInputFocused,
showThumbnail = true,
}: ObjectDetailsTabProps) {
const { t } = useTranslation(["views/explore", "views/faceLibrary"]);
@ -873,66 +997,71 @@ function ObjectDetailsTab({
<div className="text-sm">{formattedDate}</div>
</div>
</div>
<div className="flex w-full flex-col gap-2 pl-6">
<img
className="aspect-video select-none rounded-lg object-contain transition-opacity"
style={
isIOS
? {
WebkitUserSelect: "none",
WebkitTouchCallout: "none",
}
: undefined
}
draggable={false}
src={`${apiHost}api/events/${search.id}/thumbnail.webp`}
/>
<div
className={cn("flex w-full flex-row gap-2", isMobile && "flex-col")}
>
{config?.semantic_search.enabled &&
setSimilarity != undefined &&
search.data.type == "object" && (
<Button
{showThumbnail && (
<div className="flex w-full flex-col gap-2 pl-6">
<img
className="aspect-video select-none rounded-lg object-contain transition-opacity"
style={
isIOS
? {
WebkitUserSelect: "none",
WebkitTouchCallout: "none",
}
: undefined
}
draggable={false}
src={`${apiHost}api/events/${search.id}/thumbnail.webp`}
/>
<div
className={cn(
"flex w-full flex-row gap-2",
isMobile && "flex-col",
)}
>
{config?.semantic_search.enabled &&
setSimilarity != undefined &&
search.data.type == "object" && (
<Button
className="w-full"
aria-label={t("itemMenu.findSimilar.aria")}
onClick={() => {
setSearch(undefined);
setSimilarity();
}}
>
<div className="flex gap-1">
<LuSearch />
{t("itemMenu.findSimilar.label")}
</div>
</Button>
)}
{hasFace && (
<FaceSelectionDialog
className="w-full"
aria-label={t("itemMenu.findSimilar.aria")}
onClick={() => {
setSearch(undefined);
setSimilarity();
}}
faceNames={faceNames}
onTrainAttempt={onTrainFace}
>
<div className="flex gap-1">
<LuSearch />
{t("itemMenu.findSimilar.label")}
</div>
</Button>
)}
{hasFace && (
<FaceSelectionDialog
className="w-full"
faceNames={faceNames}
onTrainAttempt={onTrainFace}
>
<Button className="w-full">
<div className="flex gap-1">
<TbFaceId />
{t("trainFace", { ns: "views/faceLibrary" })}
</div>
</Button>
</FaceSelectionDialog>
)}
{config?.cameras[search?.camera].audio_transcription.enabled &&
search?.label == "speech" &&
search?.end_time && (
<Button className="w-full" onClick={onTranscribe}>
<div className="flex gap-1">
<CgTranscript />
{t("itemMenu.audioTranscription.label")}
</div>
</Button>
<Button className="w-full">
<div className="flex gap-1">
<TbFaceId />
{t("trainFace", { ns: "views/faceLibrary" })}
</div>
</Button>
</FaceSelectionDialog>
)}
{config?.cameras[search?.camera].audio_transcription.enabled &&
search?.label == "speech" &&
search?.end_time && (
<Button className="w-full" onClick={onTranscribe}>
<div className="flex gap-1">
<CgTranscript />
{t("itemMenu.audioTranscription.label")}
</div>
</Button>
)}
</div>
</div>
</div>
)}
</div>
<div className="flex flex-col gap-1.5">
{config?.cameras[search.camera].objects.genai.enabled &&
@ -1167,7 +1296,7 @@ export function ObjectSnapshotTab({
search.label != "on_demand" && (
<Card className="p-1 text-sm md:p-2">
<CardContent className="flex flex-col items-center justify-between gap-3 p-2 md:flex-row">
<div className={cn("flex flex-col space-y-3")}>
<div className={cn("flex max-w-sm flex-col space-y-3")}>
<div className={"text-lg leading-none"}>
{t("explore.plus.submitToPlus.label")}
</div>
@ -1176,7 +1305,7 @@ export function ObjectSnapshotTab({
</div>
</div>
<div className="flex w-full flex-1 flex-col justify-center gap-2 md:ml-8 md:w-auto md:justify-end">
<div className="flex w-full flex-1 flex-col justify-center gap-2 md:ml-8 md:flex-1 md:justify-end">
{state == "reviewing" && (
<>
<div>

File diff suppressed because it is too large Load Diff

View File

@ -57,7 +57,7 @@ export default function DetailStream({
elementRef: scrollRef,
});
const effectiveTime = currentTime + annotationOffset / 1000;
const effectiveTime = currentTime - annotationOffset / 1000;
const [upload, setUpload] = useState<Event | undefined>(undefined);
const [controlsExpanded, setControlsExpanded] = useState(false);
const [alwaysExpandActive, setAlwaysExpandActive] = usePersistence(
@ -213,6 +213,7 @@ export default function DetailStream({
config={config}
onSeek={onSeekCheckPlaying}
effectiveTime={effectiveTime}
annotationOffset={annotationOffset}
isActive={activeReviewId == id}
onActivate={() => setActiveReviewId(id)}
onOpenUpload={(e) => setUpload(e)}
@ -278,6 +279,7 @@ type ReviewGroupProps = {
onActivate?: () => void;
onOpenUpload?: (e: Event) => void;
effectiveTime?: number;
annotationOffset: number;
alwaysExpandActive?: boolean;
};
@ -290,11 +292,14 @@ function ReviewGroup({
onActivate,
onOpenUpload,
effectiveTime,
annotationOffset,
alwaysExpandActive = false,
}: ReviewGroupProps) {
const { t } = useTranslation("views/events");
const [open, setOpen] = useState(false);
const start = review.start_time ?? 0;
// review.start_time is in detect time, convert to record for seeking
const startRecord = start + annotationOffset / 1000;
// Auto-expand when this review becomes active and alwaysExpandActive is enabled
useEffect(() => {
@ -371,7 +376,7 @@ function ReviewGroup({
)}
onClick={() => {
onActivate?.();
onSeek(start);
onSeek(startRecord);
}}
>
<div className="ml-4 mr-2 mt-1.5 flex flex-row items-start">
@ -450,6 +455,7 @@ function ReviewGroup({
key={event.id}
event={event}
effectiveTime={effectiveTime}
annotationOffset={annotationOffset}
onSeek={onSeek}
onOpenUpload={onOpenUpload}
/>
@ -483,12 +489,14 @@ function ReviewGroup({
type EventListProps = {
event: Event;
effectiveTime?: number;
annotationOffset: number;
onSeek: (ts: number, play?: boolean) => void;
onOpenUpload?: (e: Event) => void;
};
function EventList({
event,
effectiveTime,
annotationOffset,
onSeek,
onOpenUpload,
}: EventListProps) {
@ -505,14 +513,17 @@ function EventList({
if (event) {
setSelectedObjectIds([]);
setSelectedObjectIds([event.id]);
onSeek(event.start_time);
// event.start_time is detect time, convert to record
const recordTime = event.start_time + annotationOffset / 1000;
onSeek(recordTime);
} else {
setSelectedObjectIds([]);
}
};
const handleTimelineClick = (ts: number, play?: boolean) => {
handleObjectSelect(event);
setSelectedObjectIds([]);
setSelectedObjectIds([event.id]);
onSeek(ts, play);
};
@ -554,7 +565,6 @@ function EventList({
)}
onClick={(e) => {
e.stopPropagation();
onSeek(event.start_time);
handleObjectSelect(event);
}}
role="button"
@ -568,7 +578,6 @@ function EventList({
className="flex flex-1 items-center gap-2"
onClick={(e) => {
e.stopPropagation();
onSeek(event.start_time);
handleObjectSelect(event);
}}
role="button"
@ -607,6 +616,7 @@ function EventList({
eventId={event.id}
onSeek={handleTimelineClick}
effectiveTime={effectiveTime}
annotationOffset={annotationOffset}
startTime={event.start_time}
endTime={event.end_time}
/>
@ -621,6 +631,7 @@ type LifecycleItemProps = {
isActive?: boolean;
onSeek?: (timestamp: number, play?: boolean) => void;
effectiveTime?: number;
annotationOffset: number;
isTimelineActive?: boolean;
};
@ -629,6 +640,7 @@ function LifecycleItem({
isActive,
onSeek,
effectiveTime,
annotationOffset,
isTimelineActive = false,
}: LifecycleItemProps) {
const { t } = useTranslation("views/events");
@ -682,7 +694,8 @@ function LifecycleItem({
<div
role="button"
onClick={() => {
onSeek?.(item.timestamp, false);
const recordTimestamp = item.timestamp + annotationOffset / 1000;
onSeek?.(recordTimestamp, false);
}}
className={cn(
"flex cursor-pointer items-center gap-2 text-sm text-primary-variant",
@ -751,12 +764,14 @@ function ObjectTimeline({
eventId,
onSeek,
effectiveTime,
annotationOffset,
startTime,
endTime,
}: {
eventId: string;
onSeek: (ts: number, play?: boolean) => void;
effectiveTime?: number;
annotationOffset: number;
startTime?: number;
endTime?: number;
}) {
@ -857,6 +872,7 @@ function ObjectTimeline({
onSeek={onSeek}
isActive={isActive}
effectiveTime={effectiveTime}
annotationOffset={annotationOffset}
isTimelineActive={isWithinEventRange}
/>
);

View File

@ -22,6 +22,7 @@ interface DetailStreamProviderProps {
isDetailMode: boolean;
currentTime: number;
camera: string;
initialSelectedObjectIds?: string[];
}
export function DetailStreamProvider({
@ -29,8 +30,11 @@ export function DetailStreamProvider({
isDetailMode,
currentTime,
camera,
initialSelectedObjectIds,
}: DetailStreamProviderProps) {
const [selectedObjectIds, setSelectedObjectIds] = useState<string[]>([]);
const [selectedObjectIds, setSelectedObjectIds] = useState<string[]>(
() => initialSelectedObjectIds ?? [],
);
const toggleObjectSelection = (id: string | undefined) => {
if (id === undefined) {

View File

@ -2,7 +2,7 @@ import { baseUrl } from "@/api/baseUrl";
import ClassificationModelWizardDialog from "@/components/classification/ClassificationModelWizardDialog";
import ActivityIndicator from "@/components/indicators/activity-indicator";
import { ImageShadowOverlay } from "@/components/overlay/ImageShadowOverlay";
import { Button } from "@/components/ui/button";
import { Button, buttonVariants } from "@/components/ui/button";
import { ToggleGroup, ToggleGroupItem } from "@/components/ui/toggle-group";
import useOptimisticState from "@/hooks/use-optimistic-state";
import { cn } from "@/lib/utils";
@ -10,13 +10,35 @@ import {
CustomClassificationModelConfig,
FrigateConfig,
} from "@/types/frigateConfig";
import { useEffect, useMemo, useState } from "react";
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
import { useTranslation } from "react-i18next";
import { FaFolderPlus } from "react-icons/fa";
import { MdModelTraining } from "react-icons/md";
import { LuTrash2 } from "react-icons/lu";
import { FiMoreVertical } from "react-icons/fi";
import useSWR from "swr";
import Heading from "@/components/ui/heading";
import { useOverlayState } from "@/hooks/use-overlay-state";
import axios from "axios";
import { toast } from "sonner";
import useKeyboardListener from "@/hooks/use-keyboard-listener";
import {
DropdownMenu,
DropdownMenuContent,
DropdownMenuItem,
DropdownMenuTrigger,
} from "@/components/ui/dropdown-menu";
import {
AlertDialog,
AlertDialogAction,
AlertDialogCancel,
AlertDialogContent,
AlertDialogDescription,
AlertDialogFooter,
AlertDialogHeader,
AlertDialogTitle,
} from "@/components/ui/alert-dialog";
import BlurredIconButton from "@/components/button/BlurredIconButton";
const allModelTypes = ["objects", "states"] as const;
type ModelType = (typeof allModelTypes)[number];
@ -126,7 +148,7 @@ export default function ModelSelectionView({
onClick={() => setNewModel(true)}
>
<FaFolderPlus />
Add Classification
{t("button.addClassification")}
</Button>
</div>
</div>
@ -142,6 +164,7 @@ export default function ModelSelectionView({
key={config.name}
config={config}
onClick={() => onClick(config)}
onDelete={() => refreshConfig()}
/>
))}
</div>
@ -179,12 +202,53 @@ function NoModelsView({
type ModelCardProps = {
config: CustomClassificationModelConfig;
onClick: () => void;
onDelete: () => void;
};
function ModelCard({ config, onClick }: ModelCardProps) {
function ModelCard({ config, onClick, onDelete }: ModelCardProps) {
const { t } = useTranslation(["views/classificationModel"]);
const { data: dataset } = useSWR<{
[id: string]: string[];
}>(`classification/${config.name}/dataset`, { revalidateOnFocus: false });
const [deleteDialogOpen, setDeleteDialogOpen] = useState(false);
const bypassDialogRef = useRef(false);
useKeyboardListener(["Shift"], (_, modifiers) => {
bypassDialogRef.current = modifiers.shift;
return false;
});
const handleDelete = useCallback(async () => {
await axios
.delete(`classification/${config.name}`)
.then((resp) => {
if (resp.status == 200) {
toast.success(t("toast.success.deletedModel", { count: 1 }), {
position: "top-center",
});
onDelete();
}
})
.catch((error) => {
const errorMessage =
error.response?.data?.message ||
error.response?.data?.detail ||
"Unknown error";
toast.error(t("toast.error.deleteModelFailed", { errorMessage }), {
position: "top-center",
});
});
}, [config, onDelete, t]);
const handleDeleteClick = useCallback(() => {
if (bypassDialogRef.current) {
handleDelete();
} else {
setDeleteDialogOpen(true);
}
}, [handleDelete]);
const coverImage = useMemo(() => {
if (!dataset) {
return undefined;
@ -204,22 +268,66 @@ function ModelCard({ config, onClick }: ModelCardProps) {
}, [dataset]);
return (
<div
key={config.name}
className={cn(
"relative aspect-square w-full cursor-pointer overflow-hidden rounded-lg",
"outline-transparent duration-500",
)}
onClick={() => onClick()}
>
<img
className="size-full"
src={`${baseUrl}clips/${config.name}/dataset/${coverImage?.name}/${coverImage?.img}`}
/>
<ImageShadowOverlay />
<div className="absolute bottom-2 left-3 text-lg smart-capitalize">
{config.name}
<>
<AlertDialog
open={deleteDialogOpen}
onOpenChange={() => setDeleteDialogOpen(!deleteDialogOpen)}
>
<AlertDialogContent>
<AlertDialogHeader>
<AlertDialogTitle>{t("deleteModel.title")}</AlertDialogTitle>
</AlertDialogHeader>
<AlertDialogDescription>
{t("deleteModel.single", { name: config.name })}
</AlertDialogDescription>
<AlertDialogFooter>
<AlertDialogCancel>
{t("button.cancel", { ns: "common" })}
</AlertDialogCancel>
<AlertDialogAction
className={buttonVariants({ variant: "destructive" })}
onClick={handleDelete}
>
{t("button.delete", { ns: "common" })}
</AlertDialogAction>
</AlertDialogFooter>
</AlertDialogContent>
</AlertDialog>
<div
className={cn(
"relative aspect-square w-full cursor-pointer overflow-hidden rounded-lg",
)}
onClick={onClick}
>
<img
className="size-full"
src={`${baseUrl}clips/${config.name}/dataset/${coverImage?.name}/${coverImage?.img}`}
/>
<ImageShadowOverlay />
<div className="absolute bottom-2 left-3 text-lg text-white smart-capitalize">
{config.name}
</div>
<div className="absolute bottom-2 right-2 z-40">
<DropdownMenu>
<DropdownMenuTrigger asChild onClick={(e) => e.stopPropagation()}>
<BlurredIconButton>
<FiMoreVertical className="size-5 text-white" />
</BlurredIconButton>
</DropdownMenuTrigger>
<DropdownMenuContent align="end">
<DropdownMenuItem onClick={handleDeleteClick}>
<LuTrash2 className="mr-2 size-4" />
<span>
{bypassDialogRef.current
? t("button.deleteNow", { ns: "common" })
: t("button.delete", { ns: "common" })}
</span>
</DropdownMenuItem>
</DropdownMenuContent>
</DropdownMenu>
</div>
</div>
</div>
</>
);
}

View File

@ -893,7 +893,7 @@ function ObjectTrainGrid({
// selection
const [selectedEvent, setSelectedEvent] = useState<Event>();
const [dialogTab, setDialogTab] = useState<SearchTab>("details");
const [dialogTab, setDialogTab] = useState<SearchTab>("snapshot");
// handlers

View File

@ -214,7 +214,7 @@ export default function SearchView({
// detail
const [searchDetail, setSearchDetail] = useState<SearchResult>();
const [page, setPage] = useState<SearchTab>("details");
const [page, setPage] = useState<SearchTab>("snapshot");
// search interaction
@ -222,7 +222,7 @@ export default function SearchView({
const itemRefs = useRef<(HTMLDivElement | null)[]>([]);
const onSelectSearch = useCallback(
(item: SearchResult, ctrl: boolean, page: SearchTab = "details") => {
(item: SearchResult, ctrl: boolean, page: SearchTab = "snapshot") => {
if (selectedObjects.length > 1 || ctrl) {
const index = selectedObjects.indexOf(item.id);