mirror of
https://github.com/blakeblackshear/frigate.git
synced 2026-02-15 07:35:27 +03:00
Add progress to UI when embeddings are reindexing
This commit is contained in:
parent
d07abe2143
commit
464b9be6a5
@ -85,6 +85,7 @@ CLEAR_ONGOING_REVIEW_SEGMENTS = "clear_ongoing_review_segments"
|
|||||||
UPDATE_CAMERA_ACTIVITY = "update_camera_activity"
|
UPDATE_CAMERA_ACTIVITY = "update_camera_activity"
|
||||||
UPDATE_EVENT_DESCRIPTION = "update_event_description"
|
UPDATE_EVENT_DESCRIPTION = "update_event_description"
|
||||||
UPDATE_MODEL_STATE = "update_model_state"
|
UPDATE_MODEL_STATE = "update_model_state"
|
||||||
|
UPDATE_EMBEDDINGS_REINDEX_PROGRESS = "handle_embeddings_reindex_progress"
|
||||||
|
|
||||||
# Stats Values
|
# Stats Values
|
||||||
|
|
||||||
|
|||||||
@ -10,7 +10,7 @@ from playhouse.shortcuts import model_to_dict
|
|||||||
|
|
||||||
from frigate.comms.inter_process import InterProcessRequestor
|
from frigate.comms.inter_process import InterProcessRequestor
|
||||||
from frigate.config.semantic_search import SemanticSearchConfig
|
from frigate.config.semantic_search import SemanticSearchConfig
|
||||||
from frigate.const import UPDATE_MODEL_STATE
|
from frigate.const import UPDATE_EMBEDDINGS_REINDEX_PROGRESS, UPDATE_MODEL_STATE
|
||||||
from frigate.db.sqlitevecq import SqliteVecQueueDatabase
|
from frigate.db.sqlitevecq import SqliteVecQueueDatabase
|
||||||
from frigate.models import Event
|
from frigate.models import Event
|
||||||
from frigate.types import ModelStatusTypesEnum
|
from frigate.types import ModelStatusTypesEnum
|
||||||
@ -165,19 +165,36 @@ class Embeddings:
|
|||||||
return embedding
|
return embedding
|
||||||
|
|
||||||
def reindex(self) -> None:
|
def reindex(self) -> None:
|
||||||
logger.info("Indexing event embeddings...")
|
logger.info("Indexing tracked object embeddings...")
|
||||||
|
|
||||||
self._drop_tables()
|
self._drop_tables()
|
||||||
self._create_tables()
|
self._create_tables()
|
||||||
|
|
||||||
st = time.time()
|
st = time.time()
|
||||||
totals = {
|
totals = {
|
||||||
"thumb": 0,
|
"thumbnails": 0,
|
||||||
"desc": 0,
|
"descriptions": 0,
|
||||||
|
"processed_objects": 0,
|
||||||
|
"total_objects": 0,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
self.requestor.send_data(UPDATE_EMBEDDINGS_REINDEX_PROGRESS, totals)
|
||||||
|
|
||||||
|
# Get total count of events to process
|
||||||
|
total_events = (
|
||||||
|
Event.select()
|
||||||
|
.where(
|
||||||
|
(Event.has_clip == True | Event.has_snapshot == True)
|
||||||
|
& Event.thumbnail.is_null(False)
|
||||||
|
)
|
||||||
|
.count()
|
||||||
|
)
|
||||||
|
totals["total_objects"] = total_events
|
||||||
|
|
||||||
batch_size = 100
|
batch_size = 100
|
||||||
current_page = 1
|
current_page = 1
|
||||||
|
processed_events = 0
|
||||||
|
|
||||||
events = (
|
events = (
|
||||||
Event.select()
|
Event.select()
|
||||||
.where(
|
.where(
|
||||||
@ -193,11 +210,27 @@ class Embeddings:
|
|||||||
for event in events:
|
for event in events:
|
||||||
thumbnail = base64.b64decode(event.thumbnail)
|
thumbnail = base64.b64decode(event.thumbnail)
|
||||||
self.upsert_thumbnail(event.id, thumbnail)
|
self.upsert_thumbnail(event.id, thumbnail)
|
||||||
totals["thumb"] += 1
|
totals["thumbnails"] += 1
|
||||||
|
|
||||||
if description := event.data.get("description", "").strip():
|
if description := event.data.get("description", "").strip():
|
||||||
totals["desc"] += 1
|
totals["descriptions"] += 1
|
||||||
self.upsert_description(event.id, description)
|
self.upsert_description(event.id, description)
|
||||||
|
|
||||||
|
totals["processed_objects"] += 1
|
||||||
|
|
||||||
|
# display progress debug message every batch_size events
|
||||||
|
progress = (processed_events / total_events) * 100
|
||||||
|
logger.debug(
|
||||||
|
"Processed %d/%d events (%.2f%% complete) | Thumbnails: %d, Descriptions: %d",
|
||||||
|
processed_events,
|
||||||
|
total_events,
|
||||||
|
progress,
|
||||||
|
totals["thumbnails"],
|
||||||
|
totals["descriptions"],
|
||||||
|
)
|
||||||
|
self.requestor.send_data(UPDATE_EMBEDDINGS_REINDEX_PROGRESS, totals)
|
||||||
|
|
||||||
|
# Move to the next page
|
||||||
current_page += 1
|
current_page += 1
|
||||||
events = (
|
events = (
|
||||||
Event.select()
|
Event.select()
|
||||||
@ -211,7 +244,8 @@ class Embeddings:
|
|||||||
|
|
||||||
logger.info(
|
logger.info(
|
||||||
"Embedded %d thumbnails and %d descriptions in %s seconds",
|
"Embedded %d thumbnails and %d descriptions in %s seconds",
|
||||||
totals["thumb"],
|
totals["thumbnails"],
|
||||||
totals["desc"],
|
totals["descriptions"],
|
||||||
time.time() - st,
|
time.time() - st,
|
||||||
)
|
)
|
||||||
|
self.requestor.send_data(UPDATE_EMBEDDINGS_REINDEX_PROGRESS, totals)
|
||||||
|
|||||||
@ -2,6 +2,7 @@ import { baseUrl } from "./baseUrl";
|
|||||||
import { useCallback, useEffect, useState } from "react";
|
import { useCallback, useEffect, useState } from "react";
|
||||||
import useWebSocket, { ReadyState } from "react-use-websocket";
|
import useWebSocket, { ReadyState } from "react-use-websocket";
|
||||||
import {
|
import {
|
||||||
|
EmbeddingsReindexProgressType,
|
||||||
FrigateCameraState,
|
FrigateCameraState,
|
||||||
FrigateEvent,
|
FrigateEvent,
|
||||||
FrigateReview,
|
FrigateReview,
|
||||||
@ -302,6 +303,42 @@ export function useModelState(
|
|||||||
return { payload: data ? data[model] : undefined };
|
return { payload: data ? data[model] : undefined };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useEmbeddingsReindexProgress(
|
||||||
|
revalidateOnFocus: boolean = true,
|
||||||
|
): {
|
||||||
|
payload: EmbeddingsReindexProgressType;
|
||||||
|
} {
|
||||||
|
const {
|
||||||
|
value: { payload },
|
||||||
|
send: sendCommand,
|
||||||
|
} = useWs("embeddings_reindex_progress", "embeddingsReindexProgress");
|
||||||
|
|
||||||
|
const data = useDeepMemo(JSON.parse(payload as string));
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
let listener = undefined;
|
||||||
|
if (revalidateOnFocus) {
|
||||||
|
sendCommand("embeddingsReindexProgress");
|
||||||
|
listener = () => {
|
||||||
|
if (document.visibilityState == "visible") {
|
||||||
|
sendCommand("embeddingsReindexProgress");
|
||||||
|
}
|
||||||
|
};
|
||||||
|
addEventListener("visibilitychange", listener);
|
||||||
|
}
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (listener) {
|
||||||
|
removeEventListener("visibilitychange", listener);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
// we know that these deps are correct
|
||||||
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
|
}, [revalidateOnFocus]);
|
||||||
|
|
||||||
|
return { payload: data };
|
||||||
|
}
|
||||||
|
|
||||||
export function useMotionActivity(camera: string): { payload: string } {
|
export function useMotionActivity(camera: string): { payload: string } {
|
||||||
const {
|
const {
|
||||||
value: { payload },
|
value: { payload },
|
||||||
|
|||||||
@ -1,5 +1,10 @@
|
|||||||
import { useEventUpdate, useModelState } from "@/api/ws";
|
import {
|
||||||
|
useEmbeddingsReindexProgress,
|
||||||
|
useEventUpdate,
|
||||||
|
useModelState,
|
||||||
|
} from "@/api/ws";
|
||||||
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
import ActivityIndicator from "@/components/indicators/activity-indicator";
|
||||||
|
import AnimatedCircularProgressBar from "@/components/ui/circular-progress-bar";
|
||||||
import { useApiFilterArgs } from "@/hooks/use-api-filter";
|
import { useApiFilterArgs } from "@/hooks/use-api-filter";
|
||||||
import { useTimezone } from "@/hooks/use-date-utils";
|
import { useTimezone } from "@/hooks/use-date-utils";
|
||||||
import { FrigateConfig } from "@/types/frigateConfig";
|
import { FrigateConfig } from "@/types/frigateConfig";
|
||||||
@ -182,6 +187,18 @@ export default function Explore() {
|
|||||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||||
}, [eventUpdate]);
|
}, [eventUpdate]);
|
||||||
|
|
||||||
|
// embeddings reindex progress
|
||||||
|
|
||||||
|
const { payload: reindexProgress } = useEmbeddingsReindexProgress();
|
||||||
|
|
||||||
|
const embeddingsReindexing = useMemo(
|
||||||
|
() =>
|
||||||
|
reindexProgress
|
||||||
|
? reindexProgress.total_objects - reindexProgress.processed_objects > 0
|
||||||
|
: undefined,
|
||||||
|
[reindexProgress],
|
||||||
|
);
|
||||||
|
|
||||||
// model states
|
// model states
|
||||||
|
|
||||||
const { payload: textModelState } = useModelState(
|
const { payload: textModelState } = useModelState(
|
||||||
@ -238,59 +255,101 @@ export default function Explore() {
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{config?.semantic_search.enabled && !allModelsLoaded ? (
|
{config?.semantic_search.enabled &&
|
||||||
|
(!allModelsLoaded || embeddingsReindexing) ? (
|
||||||
<div className="absolute inset-0 left-1/2 top-1/2 flex h-96 w-96 -translate-x-1/2 -translate-y-1/2">
|
<div className="absolute inset-0 left-1/2 top-1/2 flex h-96 w-96 -translate-x-1/2 -translate-y-1/2">
|
||||||
<div className="flex flex-col items-center justify-center space-y-3 rounded-lg bg-background/50 p-5">
|
<div className="flex max-w-96 flex-col items-center justify-center space-y-3 rounded-lg bg-background/50 p-5">
|
||||||
<div className="my-5 flex flex-col items-center gap-2 text-xl">
|
<div className="my-5 flex flex-col items-center gap-2 text-xl">
|
||||||
<TbExclamationCircle className="mb-3 size-10" />
|
<TbExclamationCircle className="mb-3 size-10" />
|
||||||
<div>Search Unavailable</div>
|
<div>Search Unavailable</div>
|
||||||
</div>
|
</div>
|
||||||
<div className="max-w-96 text-center">
|
{embeddingsReindexing && (
|
||||||
Frigate is downloading the necessary embeddings models to support
|
<>
|
||||||
semantic searching. This may take several minutes depending on the
|
<div className="text-center text-primary-variant">
|
||||||
speed of your network connection.
|
Search can be used after tracked object embeddings have
|
||||||
</div>
|
finished reindexing.
|
||||||
<div className="flex w-96 flex-col gap-2 py-5">
|
</div>
|
||||||
<div className="flex flex-row items-center justify-center gap-2">
|
<div className="pt-5 text-center">
|
||||||
{renderModelStateIcon(visionModelState)}
|
<AnimatedCircularProgressBar
|
||||||
Vision model
|
min={0}
|
||||||
</div>
|
max={reindexProgress.total_objects}
|
||||||
<div className="flex flex-row items-center justify-center gap-2">
|
value={reindexProgress.processed_objects}
|
||||||
{renderModelStateIcon(visionFeatureExtractorState)}
|
gaugePrimaryColor="hsl(var(--selected))"
|
||||||
Vision model feature extractor
|
gaugeSecondaryColor="hsl(var(--secondary))"
|
||||||
</div>
|
/>
|
||||||
<div className="flex flex-row items-center justify-center gap-2">
|
</div>
|
||||||
{renderModelStateIcon(textModelState)}
|
<div className="flex w-96 flex-col gap-2 py-5">
|
||||||
Text model
|
<div className="flex flex-row items-center justify-center gap-3">
|
||||||
</div>
|
<span className="text-primary-variant">
|
||||||
<div className="flex flex-row items-center justify-center gap-2">
|
Thumbnails embedded:
|
||||||
{renderModelStateIcon(textTokenizerState)}
|
</span>
|
||||||
Text tokenizer
|
{reindexProgress.thumbnails}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
<div className="flex flex-row items-center justify-center gap-3">
|
||||||
{(textModelState === "error" ||
|
<span className="text-primary-variant">
|
||||||
textTokenizerState === "error" ||
|
Descriptions embedded:
|
||||||
visionModelState === "error" ||
|
</span>
|
||||||
visionFeatureExtractorState === "error") && (
|
{reindexProgress.descriptions}
|
||||||
<div className="my-3 max-w-96 text-center text-danger">
|
</div>
|
||||||
An error has occurred. Check Frigate logs.
|
<div className="flex flex-row items-center justify-center gap-3">
|
||||||
</div>
|
<span className="text-primary-variant">
|
||||||
|
Tracked objects processed:
|
||||||
|
</span>
|
||||||
|
{reindexProgress.processed_objects}
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
|
)}
|
||||||
|
{!allModelsLoaded && (
|
||||||
|
<>
|
||||||
|
<div className="text-center text-primary-variant">
|
||||||
|
Frigate is downloading the necessary embeddings models to
|
||||||
|
support semantic searching. This may take several minutes
|
||||||
|
depending on the speed of your network connection.
|
||||||
|
</div>
|
||||||
|
<div className="flex w-96 flex-col gap-2 py-5">
|
||||||
|
<div className="flex flex-row items-center justify-center gap-2">
|
||||||
|
{renderModelStateIcon(visionModelState)}
|
||||||
|
Vision model
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row items-center justify-center gap-2">
|
||||||
|
{renderModelStateIcon(visionFeatureExtractorState)}
|
||||||
|
Vision model feature extractor
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row items-center justify-center gap-2">
|
||||||
|
{renderModelStateIcon(textModelState)}
|
||||||
|
Text model
|
||||||
|
</div>
|
||||||
|
<div className="flex flex-row items-center justify-center gap-2">
|
||||||
|
{renderModelStateIcon(textTokenizerState)}
|
||||||
|
Text tokenizer
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
{(textModelState === "error" ||
|
||||||
|
textTokenizerState === "error" ||
|
||||||
|
visionModelState === "error" ||
|
||||||
|
visionFeatureExtractorState === "error") && (
|
||||||
|
<div className="my-3 max-w-96 text-center text-danger">
|
||||||
|
An error has occurred. Check Frigate logs.
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
<div className="text-center text-primary-variant">
|
||||||
|
You may want to reindex the embeddings of your tracked objects
|
||||||
|
once the models are downloaded.
|
||||||
|
</div>
|
||||||
|
<div className="flex items-center text-primary-variant">
|
||||||
|
<Link
|
||||||
|
to="https://docs.frigate.video/configuration/semantic_search"
|
||||||
|
target="_blank"
|
||||||
|
rel="noopener noreferrer"
|
||||||
|
className="inline"
|
||||||
|
>
|
||||||
|
Read the documentation{" "}
|
||||||
|
<LuExternalLink className="ml-2 inline-flex size-3" />
|
||||||
|
</Link>
|
||||||
|
</div>
|
||||||
|
</>
|
||||||
)}
|
)}
|
||||||
<div className="max-w-96 text-center">
|
|
||||||
You may want to reindex the embeddings of your tracked objects
|
|
||||||
once the models are downloaded.
|
|
||||||
</div>
|
|
||||||
<div className="flex max-w-96 items-center text-primary-variant">
|
|
||||||
<Link
|
|
||||||
to="https://docs.frigate.video/configuration/semantic_search"
|
|
||||||
target="_blank"
|
|
||||||
rel="noopener noreferrer"
|
|
||||||
className="inline"
|
|
||||||
>
|
|
||||||
Read the documentation{" "}
|
|
||||||
<LuExternalLink className="ml-2 inline-flex size-3" />
|
|
||||||
</Link>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@ -62,4 +62,11 @@ export type ModelState =
|
|||||||
| "downloaded"
|
| "downloaded"
|
||||||
| "error";
|
| "error";
|
||||||
|
|
||||||
|
export type EmbeddingsReindexProgressType = {
|
||||||
|
thumbnails: number;
|
||||||
|
descriptions: number;
|
||||||
|
processed_objects: number;
|
||||||
|
total_objects: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type ToggleableSetting = "ON" | "OFF";
|
export type ToggleableSetting = "ON" | "OFF";
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user