mirror of
https://github.com/blakeblackshear/frigate.git
synced 2025-12-06 13:34:13 +03:00
Compare commits
5 Commits
130dc76a01
...
bbe18d5d6b
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
bbe18d5d6b | ||
|
|
b3abede6cb | ||
|
|
7544d9caf8 | ||
|
|
eb75c76ed4 | ||
|
|
12e5f274dd |
@ -209,10 +209,22 @@ class ReviewDescriptionProcessor(PostProcessorApi):
|
|||||||
logger.debug(
|
logger.debug(
|
||||||
f"Found GenAI Review Summary request for {start_ts} to {end_ts}"
|
f"Found GenAI Review Summary request for {start_ts} to {end_ts}"
|
||||||
)
|
)
|
||||||
items: list[dict[str, Any]] = [
|
|
||||||
r["data"]["metadata"]
|
# Query all review segments with camera and time information
|
||||||
|
segments: list[dict[str, Any]] = [
|
||||||
|
{
|
||||||
|
"camera": r["camera"].replace("_", " ").title(),
|
||||||
|
"start_time": r["start_time"],
|
||||||
|
"end_time": r["end_time"],
|
||||||
|
"metadata": r["data"]["metadata"],
|
||||||
|
}
|
||||||
for r in (
|
for r in (
|
||||||
ReviewSegment.select(ReviewSegment.data)
|
ReviewSegment.select(
|
||||||
|
ReviewSegment.camera,
|
||||||
|
ReviewSegment.start_time,
|
||||||
|
ReviewSegment.end_time,
|
||||||
|
ReviewSegment.data,
|
||||||
|
)
|
||||||
.where(
|
.where(
|
||||||
(ReviewSegment.data["metadata"].is_null(False))
|
(ReviewSegment.data["metadata"].is_null(False))
|
||||||
& (ReviewSegment.start_time < end_ts)
|
& (ReviewSegment.start_time < end_ts)
|
||||||
@ -224,21 +236,66 @@ class ReviewDescriptionProcessor(PostProcessorApi):
|
|||||||
)
|
)
|
||||||
]
|
]
|
||||||
|
|
||||||
if len(items) == 0:
|
if len(segments) == 0:
|
||||||
logger.debug("No review items with metadata found during time period")
|
logger.debug("No review items with metadata found during time period")
|
||||||
return "No activity was found during this time."
|
return "No activity was found during this time period."
|
||||||
|
|
||||||
important_items = list(
|
# Identify primary items (important items that need review)
|
||||||
filter(
|
primary_segments = [
|
||||||
lambda item: item.get("potential_threat_level", 0) > 0
|
seg
|
||||||
or item.get("other_concerns"),
|
for seg in segments
|
||||||
items,
|
if seg["metadata"].get("potential_threat_level", 0) > 0
|
||||||
)
|
or seg["metadata"].get("other_concerns")
|
||||||
)
|
]
|
||||||
|
|
||||||
if not important_items:
|
if not primary_segments:
|
||||||
return "No concerns were found during this time period."
|
return "No concerns were found during this time period."
|
||||||
|
|
||||||
|
# For each primary segment, find overlapping contextual items from other cameras
|
||||||
|
all_items_for_summary = []
|
||||||
|
|
||||||
|
for primary_seg in primary_segments:
|
||||||
|
# Add the primary item with marker
|
||||||
|
primary_item = copy.deepcopy(primary_seg["metadata"])
|
||||||
|
primary_item["_is_primary"] = True
|
||||||
|
primary_item["_camera"] = primary_seg["camera"]
|
||||||
|
all_items_for_summary.append(primary_item)
|
||||||
|
|
||||||
|
# Find overlapping contextual items from other cameras
|
||||||
|
primary_start = primary_seg["start_time"]
|
||||||
|
primary_end = primary_seg["end_time"]
|
||||||
|
primary_camera = primary_seg["camera"]
|
||||||
|
|
||||||
|
for seg in segments:
|
||||||
|
seg_camera = seg["camera"]
|
||||||
|
|
||||||
|
if seg_camera == primary_camera:
|
||||||
|
continue
|
||||||
|
|
||||||
|
if seg in primary_segments:
|
||||||
|
continue
|
||||||
|
|
||||||
|
seg_start = seg["start_time"]
|
||||||
|
seg_end = seg["end_time"]
|
||||||
|
|
||||||
|
if seg_start < primary_end and primary_start < seg_end:
|
||||||
|
contextual_item = copy.deepcopy(seg["metadata"])
|
||||||
|
contextual_item["_is_primary"] = False
|
||||||
|
contextual_item["_camera"] = seg_camera
|
||||||
|
contextual_item["_related_to_camera"] = primary_camera
|
||||||
|
|
||||||
|
if not any(
|
||||||
|
item.get("_camera") == seg_camera
|
||||||
|
and item.get("time") == contextual_item.get("time")
|
||||||
|
for item in all_items_for_summary
|
||||||
|
):
|
||||||
|
all_items_for_summary.append(contextual_item)
|
||||||
|
|
||||||
|
logger.debug(
|
||||||
|
f"Summary includes {len(primary_segments)} primary items and "
|
||||||
|
f"{len(all_items_for_summary) - len(primary_segments)} contextual items"
|
||||||
|
)
|
||||||
|
|
||||||
if self.config.review.genai.debug_save_thumbnails:
|
if self.config.review.genai.debug_save_thumbnails:
|
||||||
Path(
|
Path(
|
||||||
os.path.join(CLIPS_DIR, "genai-requests", f"{start_ts}-{end_ts}")
|
os.path.join(CLIPS_DIR, "genai-requests", f"{start_ts}-{end_ts}")
|
||||||
@ -247,7 +304,7 @@ class ReviewDescriptionProcessor(PostProcessorApi):
|
|||||||
return self.genai_client.generate_review_summary(
|
return self.genai_client.generate_review_summary(
|
||||||
start_ts,
|
start_ts,
|
||||||
end_ts,
|
end_ts,
|
||||||
important_items,
|
all_items_for_summary,
|
||||||
self.config.review.genai.debug_save_thumbnails,
|
self.config.review.genai.debug_save_thumbnails,
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
|
|||||||
@ -185,44 +185,66 @@ Each line represents a detection state, not necessarily unique individuals. Pare
|
|||||||
timeline_summary_prompt = f"""
|
timeline_summary_prompt = f"""
|
||||||
You are a security officer.
|
You are a security officer.
|
||||||
Time range: {time_range}.
|
Time range: {time_range}.
|
||||||
Input: JSON list with "title", "scene", "confidence", "potential_threat_level" (1-2), "other_concerns".
|
Input: JSON list with "title", "scene", "confidence", "potential_threat_level" (0-2), "other_concerns", "_is_primary", "_camera".
|
||||||
|
|
||||||
Task: Write a concise, human-presentable security report in markdown format.
|
Task: Write a concise, human-presentable security report in markdown format.
|
||||||
|
|
||||||
|
CRITICAL - Understanding Primary vs Contextual Items:
|
||||||
|
- Items with "_is_primary": true are events that REQUIRE REVIEW and MUST be included in the report
|
||||||
|
- Items with "_is_primary": false are additional context from other camera perspectives that overlap in time
|
||||||
|
- **DO NOT create separate bullet points or sections for contextual items**
|
||||||
|
- **ONLY use contextual items to enrich and inform the description of primary items**
|
||||||
|
- The "_camera" field indicates which camera captured each event
|
||||||
|
- **When a contextual item provides relevant background, you MUST incorporate it directly into the primary event's bullet point**
|
||||||
|
- Contextual information often explains or de-escalates seemingly suspicious primary events
|
||||||
|
|
||||||
Rules for the report:
|
Rules for the report:
|
||||||
|
|
||||||
- Title & overview
|
- Title & overview
|
||||||
- Start with:
|
- Start with:
|
||||||
# Security Summary - {time_range}
|
# Security Summary - {time_range}
|
||||||
- Write a 1-2 sentence situational overview capturing the general pattern of the period.
|
- Write a 1-2 sentence situational overview capturing the general pattern of the period.
|
||||||
|
- Keep the overview high-level; specific details will be in the event bullets below.
|
||||||
|
|
||||||
- Event details
|
- Event details
|
||||||
- Present events in chronological order as a bullet list.
|
- **ONLY create bullet points for PRIMARY items (_is_primary: true)**
|
||||||
- **If multiple events occur within the same minute or overlapping time range, COMBINE them into a single bullet.**
|
- **Do NOT create sections or bullets for events that don't exist**
|
||||||
- Summarize the distinct activities as sub-points under the shared timestamp.
|
- Do NOT create separate bullets for contextual items
|
||||||
- If no timestamp is given, preserve order but label as “Time not specified.”
|
- Present primary events in chronological order as a bullet list.
|
||||||
|
- **CRITICAL: When contextual items overlap with a primary event, you MUST weave that information directly into the same bullet point**
|
||||||
|
- Format: **[Timestamp]** - [Description incorporating any contextual information]. [Camera info]. (threat level: X)
|
||||||
|
- If contextual information provides an explanation (e.g., delivery truck → person is likely delivery driver), reflect this understanding in your description and potentially adjust the perceived threat level
|
||||||
|
- If multiple PRIMARY events occur within the same minute, combine them into a single bullet with sub-points.
|
||||||
- Use bold timestamps for clarity.
|
- Use bold timestamps for clarity.
|
||||||
- Group bullets under subheadings when multiple events fall into the same category (e.g., Vehicle Activity, Porch Activity, Unusual Behavior).
|
- Camera format: "Camera: [camera name]" or mention contextual cameras inline when relevant
|
||||||
|
- Group bullets under subheadings ONLY when you have actual PRIMARY events to list (e.g., Porch Activity, Unusual Behavior).
|
||||||
|
|
||||||
- Threat levels
|
- Threat levels
|
||||||
- Always show the threat level for each event using these labels:
|
- Show the threat level for PRIMARY events using these labels:
|
||||||
- Threat level 0: "Normal"
|
- Threat level 0: "Normal"
|
||||||
- Threat level 1: "Needs review"
|
- Threat level 1: "Needs review"
|
||||||
- Threat level 2: "Security concern"
|
- Threat level 2: "Security concern"
|
||||||
- Format as (threat level: Normal), (threat level: Needs review), or (threat level: Security concern).
|
- Format as (threat level: Normal), (threat level: Needs review), or (threat level: Security concern).
|
||||||
- If multiple events at the same time share the same threat level, only state it once.
|
- **When contextual items clearly explain a primary event (e.g., delivery truck explains person at door), you should describe it as normal activity and note the explanation**
|
||||||
|
- **Your description and tone should reflect the fuller understanding provided by contextual information**
|
||||||
|
- Example: Primary event says "unidentified person with face covering" but context shows delivery truck → describe as "delivery person (truck visible on Front Driveway Cam)" rather than emphasizing suspicious elements
|
||||||
|
- The stored threat level remains as originally classified, but your narrative should reflect the contextual understanding
|
||||||
|
- If multiple PRIMARY events at the same time share the same threat level, only state it once.
|
||||||
|
|
||||||
- Final assessment
|
- Final assessment
|
||||||
- End with a Final Assessment section.
|
- End with a Final Assessment section.
|
||||||
- If all events are threat level 0:
|
- If all primary events are threat level 0 or explained by contextual items:
|
||||||
Final assessment: Only normal residential activity observed during this period.
|
Final assessment: Only normal residential activity observed during this period.
|
||||||
- If threat level 1 events are present:
|
- If threat level 1 events are present:
|
||||||
Final assessment: Some activity requires review but no security concerns identified.
|
Final assessment: Some activity requires review but no security concerns identified.
|
||||||
- If threat level 2 events are present, clearly summarize them as Security concerns requiring immediate attention.
|
- If threat level 2 events are present, clearly summarize them as Security concerns requiring immediate attention.
|
||||||
|
- Keep this section brief - do not repeat details from the event descriptions above.
|
||||||
|
|
||||||
- Conciseness
|
- Conciseness
|
||||||
- Do not repeat benign clothing/appearance details unless they distinguish individuals.
|
- Do not repeat benign clothing/appearance details unless they distinguish individuals.
|
||||||
- Summarize similar routine events instead of restating full scene descriptions.
|
- Summarize similar routine events instead of restating full scene descriptions.
|
||||||
|
- When incorporating contextual information, do so briefly and naturally within the primary event description.
|
||||||
|
- Avoid lengthy explanatory notes - integrate context seamlessly into the narrative.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
for item in segments:
|
for item in segments:
|
||||||
|
|||||||
@ -23,6 +23,7 @@ import { useTranslation } from "react-i18next";
|
|||||||
import { ImageShadowOverlay } from "../overlay/ImageShadowOverlay";
|
import { ImageShadowOverlay } from "../overlay/ImageShadowOverlay";
|
||||||
import BlurredIconButton from "../button/BlurredIconButton";
|
import BlurredIconButton from "../button/BlurredIconButton";
|
||||||
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
import { Tooltip, TooltipContent, TooltipTrigger } from "../ui/tooltip";
|
||||||
|
import { useIsAdmin } from "@/hooks/use-is-admin";
|
||||||
|
|
||||||
type ExportProps = {
|
type ExportProps = {
|
||||||
className: string;
|
className: string;
|
||||||
@ -40,6 +41,7 @@ export default function ExportCard({
|
|||||||
onDelete,
|
onDelete,
|
||||||
}: ExportProps) {
|
}: ExportProps) {
|
||||||
const { t } = useTranslation(["views/exports"]);
|
const { t } = useTranslation(["views/exports"]);
|
||||||
|
const isAdmin = useIsAdmin();
|
||||||
const [hovered, setHovered] = useState(false);
|
const [hovered, setHovered] = useState(false);
|
||||||
const [loading, setLoading] = useState(
|
const [loading, setLoading] = useState(
|
||||||
exportedRecording.thumb_path.length > 0,
|
exportedRecording.thumb_path.length > 0,
|
||||||
@ -195,7 +197,7 @@ export default function ExportCard({
|
|||||||
</Tooltip>
|
</Tooltip>
|
||||||
</a>
|
</a>
|
||||||
)}
|
)}
|
||||||
{!exportedRecording.in_progress && (
|
{isAdmin && !exportedRecording.in_progress && (
|
||||||
<Tooltip>
|
<Tooltip>
|
||||||
<TooltipTrigger asChild>
|
<TooltipTrigger asChild>
|
||||||
<BlurredIconButton
|
<BlurredIconButton
|
||||||
@ -212,21 +214,23 @@ export default function ExportCard({
|
|||||||
<TooltipContent>{t("tooltip.editName")}</TooltipContent>
|
<TooltipContent>{t("tooltip.editName")}</TooltipContent>
|
||||||
</Tooltip>
|
</Tooltip>
|
||||||
)}
|
)}
|
||||||
<Tooltip>
|
{isAdmin && (
|
||||||
<TooltipTrigger asChild>
|
<Tooltip>
|
||||||
<BlurredIconButton
|
<TooltipTrigger asChild>
|
||||||
onClick={() =>
|
<BlurredIconButton
|
||||||
onDelete({
|
onClick={() =>
|
||||||
file: exportedRecording.id,
|
onDelete({
|
||||||
exportName: exportedRecording.name,
|
file: exportedRecording.id,
|
||||||
})
|
exportName: exportedRecording.name,
|
||||||
}
|
})
|
||||||
>
|
}
|
||||||
<LuTrash className="size-4 fill-destructive text-destructive hover:text-white" />
|
>
|
||||||
</BlurredIconButton>
|
<LuTrash className="size-4 fill-destructive text-destructive hover:text-white" />
|
||||||
</TooltipTrigger>
|
</BlurredIconButton>
|
||||||
<TooltipContent>{t("tooltip.deleteExport")}</TooltipContent>
|
</TooltipTrigger>
|
||||||
</Tooltip>
|
<TooltipContent>{t("tooltip.deleteExport")}</TooltipContent>
|
||||||
|
</Tooltip>
|
||||||
|
)}
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user