Add progress to UI when embeddings are reindexing

This commit is contained in:
Josh Hawkins 2024-10-10 13:59:21 -05:00
parent d07abe2143
commit 464b9be6a5
5 changed files with 194 additions and 56 deletions

View File

@ -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

View File

@ -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)

View File

@ -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 },

View File

@ -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,17 +255,57 @@ 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
finished reindexing.
</div>
<div className="pt-5 text-center">
<AnimatedCircularProgressBar
min={0}
max={reindexProgress.total_objects}
value={reindexProgress.processed_objects}
gaugePrimaryColor="hsl(var(--selected))"
gaugeSecondaryColor="hsl(var(--secondary))"
/>
</div>
<div className="flex w-96 flex-col gap-2 py-5">
<div className="flex flex-row items-center justify-center gap-3">
<span className="text-primary-variant">
Thumbnails embedded:
</span>
{reindexProgress.thumbnails}
</div>
<div className="flex flex-row items-center justify-center gap-3">
<span className="text-primary-variant">
Descriptions embedded:
</span>
{reindexProgress.descriptions}
</div>
<div className="flex flex-row items-center justify-center gap-3">
<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>
<div className="flex w-96 flex-col gap-2 py-5"> <div className="flex w-96 flex-col gap-2 py-5">
<div className="flex flex-row items-center justify-center gap-2"> <div className="flex flex-row items-center justify-center gap-2">
@ -276,11 +333,11 @@ export default function Explore() {
An error has occurred. Check Frigate logs. An error has occurred. Check Frigate logs.
</div> </div>
)} )}
<div className="max-w-96 text-center"> <div className="text-center text-primary-variant">
You may want to reindex the embeddings of your tracked objects You may want to reindex the embeddings of your tracked objects
once the models are downloaded. once the models are downloaded.
</div> </div>
<div className="flex max-w-96 items-center text-primary-variant"> <div className="flex items-center text-primary-variant">
<Link <Link
to="https://docs.frigate.video/configuration/semantic_search" to="https://docs.frigate.video/configuration/semantic_search"
target="_blank" target="_blank"
@ -291,6 +348,8 @@ export default function Explore() {
<LuExternalLink className="ml-2 inline-flex size-3" /> <LuExternalLink className="ml-2 inline-flex size-3" />
</Link> </Link>
</div> </div>
</>
)}
</div> </div>
</div> </div>
) : ( ) : (

View File

@ -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";