This commit is contained in:
Nicolas Mowen 2026-04-30 14:13:50 +00:00 committed by GitHub
commit 36006c9ff7
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
10 changed files with 102 additions and 43 deletions

View File

@ -1368,12 +1368,17 @@ def preview_gif(
file_start = f"preview_{camera_name}-" file_start = f"preview_{camera_name}-"
start_file = f"{file_start}{start_ts}.{PREVIEW_FRAME_TYPE}" start_file = f"{file_start}{start_ts}.{PREVIEW_FRAME_TYPE}"
end_file = f"{file_start}{end_ts}.{PREVIEW_FRAME_TYPE}" end_file = f"{file_start}{end_ts}.{PREVIEW_FRAME_TYPE}"
camera_files = [
entry.name
for entry in os.scandir(preview_dir)
if entry.name.startswith(file_start)
]
camera_files.sort()
selected_previews = [] selected_previews = []
for file in sorted(os.listdir(preview_dir)): for file in camera_files:
if not file.startswith(file_start):
continue
if file < start_file: if file < start_file:
continue continue
@ -1550,12 +1555,17 @@ def preview_mp4(
file_start = f"preview_{camera_name}-" file_start = f"preview_{camera_name}-"
start_file = f"{file_start}{start_ts}.{PREVIEW_FRAME_TYPE}" start_file = f"{file_start}{start_ts}.{PREVIEW_FRAME_TYPE}"
end_file = f"{file_start}{end_ts}.{PREVIEW_FRAME_TYPE}" end_file = f"{file_start}{end_ts}.{PREVIEW_FRAME_TYPE}"
camera_files = [
entry.name
for entry in os.scandir(preview_dir)
if entry.name.startswith(file_start)
]
camera_files.sort()
selected_previews = [] selected_previews = []
for file in sorted(os.listdir(preview_dir)): for file in camera_files:
if not file.startswith(file_start):
continue
if file < start_file: if file < start_file:
continue continue

View File

@ -148,12 +148,17 @@ def get_preview_frames_from_cache(camera_name: str, start_ts: float, end_ts: flo
file_start = f"preview_{camera_name}-" file_start = f"preview_{camera_name}-"
start_file = f"{file_start}{start_ts}.{PREVIEW_FRAME_TYPE}" start_file = f"{file_start}{start_ts}.{PREVIEW_FRAME_TYPE}"
end_file = f"{file_start}{end_ts}.{PREVIEW_FRAME_TYPE}" end_file = f"{file_start}{end_ts}.{PREVIEW_FRAME_TYPE}"
camera_files = [
entry.name
for entry in os.scandir(preview_dir)
if entry.name.startswith(file_start)
]
camera_files.sort()
selected_previews = [] selected_previews = []
for file in sorted(os.listdir(preview_dir)): for file in camera_files:
if not file.startswith(file_start):
continue
if file < start_file: if file < start_file:
continue continue

View File

@ -15,7 +15,7 @@ TRIGGER_DIR = f"{CLIPS_DIR}/triggers"
BIRDSEYE_PIPE = "/tmp/cache/birdseye" BIRDSEYE_PIPE = "/tmp/cache/birdseye"
CACHE_DIR = "/tmp/cache" CACHE_DIR = "/tmp/cache"
REPLAY_CAMERA_PREFIX = "_replay_" REPLAY_CAMERA_PREFIX = "_replay_"
REPLAY_DIR = os.path.join(CACHE_DIR, "replay") REPLAY_DIR = os.path.join(CLIPS_DIR, "replay")
PLUS_ENV_VAR = "PLUS_API_KEY" PLUS_ENV_VAR = "PLUS_API_KEY"
PLUS_API_HOST = "https://api.frigate.video" PLUS_API_HOST = "https://api.frigate.video"

View File

@ -366,12 +366,17 @@ class ReviewDescriptionProcessor(PostProcessorApi):
file_start = f"preview_{camera}-" file_start = f"preview_{camera}-"
start_file = f"{file_start}{start_time}.webp" start_file = f"{file_start}{start_time}.webp"
end_file = f"{file_start}{end_time}.webp" end_file = f"{file_start}{end_time}.webp"
camera_files = [
entry.name
for entry in os.scandir(preview_dir)
if entry.name.startswith(file_start)
]
camera_files.sort()
all_frames: list[str] = [] all_frames: list[str] = []
for file in sorted(os.listdir(preview_dir)): for file in camera_files:
if not file.startswith(file_start):
continue
if file < start_file: if file < start_file:
if len(all_frames): if len(all_frames):
all_frames[0] = os.path.join(preview_dir, file) all_frames[0] = os.path.join(preview_dir, file)

View File

@ -153,9 +153,6 @@ Each line represents a detection state, not necessarily unique individuals. The
if "other_concerns" in schema.get("required", []): if "other_concerns" in schema.get("required", []):
schema["required"].remove("other_concerns") schema["required"].remove("other_concerns")
# OpenAI strict mode requires additionalProperties: false on all objects
schema["additionalProperties"] = False
response_format = { response_format = {
"type": "json_schema", "type": "json_schema",
"json_schema": { "json_schema": {

View File

@ -136,11 +136,29 @@ class GeminiClient(GenAIClient):
) )
) )
elif role == "assistant": elif role == "assistant":
gemini_messages.append( parts: list[types.Part] = []
types.Content( if content:
role="model", parts=[types.Part.from_text(text=content)] parts.append(types.Part.from_text(text=content))
) for tc in msg.get("tool_calls") or []:
) func = tc.get("function") or {}
tc_name = func.get("name") or ""
tc_args: Any = func.get("arguments")
if isinstance(tc_args, str):
try:
tc_args = json.loads(tc_args)
except (json.JSONDecodeError, TypeError):
tc_args = {}
if not isinstance(tc_args, dict):
tc_args = {}
if tc_name:
parts.append(
types.Part.from_function_call(
name=tc_name, args=tc_args
)
)
if not parts:
parts.append(types.Part.from_text(text=" "))
gemini_messages.append(types.Content(role="model", parts=parts))
elif role == "tool": elif role == "tool":
# Handle tool response # Handle tool response
response_payload = ( response_payload = (
@ -151,7 +169,9 @@ class GeminiClient(GenAIClient):
role="function", role="function",
parts=[ parts=[
types.Part.from_function_response( types.Part.from_function_response(
name=msg.get("name", ""), name=msg.get("name")
or msg.get("tool_call_id")
or "",
response=response_payload, response=response_payload,
) )
], ],
@ -345,11 +365,29 @@ class GeminiClient(GenAIClient):
) )
) )
elif role == "assistant": elif role == "assistant":
gemini_messages.append( parts: list[types.Part] = []
types.Content( if content:
role="model", parts=[types.Part.from_text(text=content)] parts.append(types.Part.from_text(text=content))
) for tc in msg.get("tool_calls") or []:
) func = tc.get("function") or {}
tc_name = func.get("name") or ""
tc_args: Any = func.get("arguments")
if isinstance(tc_args, str):
try:
tc_args = json.loads(tc_args)
except (json.JSONDecodeError, TypeError):
tc_args = {}
if not isinstance(tc_args, dict):
tc_args = {}
if tc_name:
parts.append(
types.Part.from_function_call(
name=tc_name, args=tc_args
)
)
if not parts:
parts.append(types.Part.from_text(text=" "))
gemini_messages.append(types.Content(role="model", parts=parts))
elif role == "tool": elif role == "tool":
# Handle tool response # Handle tool response
response_payload = ( response_payload = (
@ -360,7 +398,9 @@ class GeminiClient(GenAIClient):
role="function", role="function",
parts=[ parts=[
types.Part.from_function_response( types.Part.from_function_response(
name=msg.get("name", ""), name=msg.get("name")
or msg.get("tool_call_id")
or "",
response=response_payload, response=response_payload,
) )
], ],

View File

@ -73,8 +73,17 @@ class OpenAIClient(GenAIClient):
**self.genai_config.runtime_options, **self.genai_config.runtime_options,
} }
if response_format: if response_format:
# OpenAI strict mode requires additionalProperties: false on the schema
if response_format.get("type") == "json_schema" and response_format.get(
"json_schema", {}
).get("strict"):
schema = response_format.get("json_schema", {}).get("schema")
if isinstance(schema, dict):
schema["additionalProperties"] = False
request_params["response_format"] = response_format request_params["response_format"] = response_format
result = self.provider.chat.completions.create(**request_params) result = self.provider.chat.completions.create(**request_params)
if ( if (
result is not None result is not None
and hasattr(result, "choices") and hasattr(result, "choices")

View File

@ -391,10 +391,8 @@ export default function MobileReviewSettingsDrawer({
className="flex w-full items-center justify-center gap-2" className="flex w-full items-center justify-center gap-2"
aria-label={t("title", { ns: "views/replay" })} aria-label={t("title", { ns: "views/replay" })}
onClick={() => { onClick={() => {
const now = new Date(latestTime * 1000);
now.setHours(now.getHours() - 1);
setDebugReplayRange({ setDebugReplayRange({
after: now.getTime() / 1000, after: latestTime - 60,
before: latestTime, before: latestTime,
}); });
setSelectedReplayOption("1"); setSelectedReplayOption("1");
@ -541,11 +539,9 @@ export default function MobileReviewSettingsDrawer({
return; return;
} }
const hours = parseInt(option); const minutes = parseInt(option, 10);
const end = latestTime; const end = latestTime;
const now = new Date(end * 1000); setDebugReplayRange({ after: end - minutes * 60, before: end });
now.setHours(now.getHours() - hours);
setDebugReplayRange({ after: now.getTime() / 1000, before: end });
}; };
content = ( content = (

View File

@ -396,7 +396,6 @@ export default function HlsVideoPlayer({
}} }}
> >
<ObjectTrackOverlay <ObjectTrackOverlay
key={`overlay-${currentTime}`}
camera={camera} camera={camera}
showBoundingBoxes={!isPlaying} showBoundingBoxes={!isPlaying}
currentTime={currentTime} currentTime={currentTime}

View File

@ -728,10 +728,8 @@ export function RecordingView({
setShareTimestampOpen(true); setShareTimestampOpen(true);
}} }}
onDebugReplayClick={() => { onDebugReplayClick={() => {
const now = new Date(timeRange.before * 1000);
now.setHours(now.getHours() - 1);
setDebugReplayRange({ setDebugReplayRange({
after: now.getTime() / 1000, after: timeRange.before - 60,
before: timeRange.before, before: timeRange.before,
}); });
setDebugReplayMode("select"); setDebugReplayMode("select");