Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 90fd1fa

Browse files
committed
updates
1 parent 9fe3b8c commit 90fd1fa

19 files changed

+402
-200
lines changed

custom_components/llmvision/__init__.py

Lines changed: 53 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,11 @@
2323
CONF_AWS_SECRET_ACCESS_KEY,
2424
CONF_AWS_REGION_NAME,
2525
CONF_AWS_DEFAULT_MODEL,
26+
CONF_OPENWEBUI_IP_ADDRESS,
27+
CONF_OPENWEBUI_PORT,
28+
CONF_OPENWEBUI_HTTPS,
29+
CONF_OPENWEBUI_API_KEY,
30+
CONF_OPENWEBUI_DEFAULT_MODEL,
2631
MESSAGE,
2732
REMEMBER,
2833
MODEL,
@@ -81,12 +86,18 @@ async def async_setup_entry(hass, entry):
8186
ollama_https = entry.data.get(CONF_OLLAMA_HTTPS)
8287
custom_openai_endpoint = entry.data.get(CONF_CUSTOM_OPENAI_ENDPOINT)
8388
custom_openai_api_key = entry.data.get(CONF_CUSTOM_OPENAI_API_KEY)
84-
custom_openai_default_model = entry.data.get(CONF_CUSTOM_OPENAI_DEFAULT_MODEL)
89+
custom_openai_default_model = entry.data.get(
90+
CONF_CUSTOM_OPENAI_DEFAULT_MODEL)
8591
retention_time = entry.data.get(CONF_RETENTION_TIME)
8692
aws_access_key_id = entry.data.get(CONF_AWS_ACCESS_KEY_ID)
8793
aws_secret_access_key = entry.data.get(CONF_AWS_SECRET_ACCESS_KEY)
8894
aws_region_name = entry.data.get(CONF_AWS_REGION_NAME)
8995
aws_default_model = entry.data.get(CONF_AWS_DEFAULT_MODEL)
96+
openwebui_ip_address = entry.data.get(CONF_OPENWEBUI_IP_ADDRESS)
97+
openwebui_port = entry.data.get(CONF_OPENWEBUI_PORT)
98+
openwebui_https = entry.data.get(CONF_OPENWEBUI_HTTPS)
99+
openwebui_api_key = entry.data.get(CONF_OPENWEBUI_API_KEY)
100+
openwebui_default_model = entry.data.get(CONF_OPENWEBUI_DEFAULT_MODEL)
90101

91102
# Ensure DOMAIN exists in hass.data
92103
if DOMAIN not in hass.data:
@@ -116,6 +127,11 @@ async def async_setup_entry(hass, entry):
116127
CONF_AWS_SECRET_ACCESS_KEY: aws_secret_access_key,
117128
CONF_AWS_REGION_NAME: aws_region_name,
118129
CONF_AWS_DEFAULT_MODEL: aws_default_model,
130+
CONF_OPENWEBUI_IP_ADDRESS: openwebui_ip_address,
131+
CONF_OPENWEBUI_PORT: openwebui_port,
132+
CONF_OPENWEBUI_HTTPS: openwebui_https,
133+
CONF_OPENWEBUI_API_KEY: openwebui_api_key,
134+
CONF_OPENWEBUI_DEFAULT_MODEL: openwebui_default_model
119135
}
120136

121137
# Filter out None values
@@ -170,7 +186,7 @@ async def async_migrate_entry(hass, config_entry: ConfigEntry) -> bool:
170186
return False
171187

172188

173-
async def _remember(hass, call, start, response) -> None:
189+
async def _remember(hass, call, start, response, key_frame) -> None:
174190
if call.remember:
175191
# Find semantic index config
176192
config_entry = None
@@ -186,37 +202,27 @@ async def _remember(hass, call, start, response) -> None:
186202

187203
semantic_index = SemanticIndex(hass, config_entry)
188204

189-
if "title" in response:
190-
title = response.get("title", "Unknown object seen")
191-
if call.image_entities and len(call.image_entities) > 0:
192-
camera_name = call.image_entities[0]
193-
elif call.video_paths and len(call.video_paths) > 0:
194-
camera_name = call.video_paths[0].split(
195-
"/")[-1].replace(".mp4", "")
196-
else:
197-
camera_name = "File Input"
198-
199-
if "title" not in response:
200-
if call.image_entities and len(call.image_entities) > 0:
201-
camera_name = call.image_entities[0]
202-
title = "Motion detected near " + camera_name
203-
elif call.video_paths and len(call.video_paths) > 0:
204-
camera_name = call.video_paths[0].split(
205-
"/")[-1].replace(".mp4", "")
206-
title = "Motion detected in " + camera_name
207-
else:
208-
camera_name = "File Input"
209-
title = "Motion detected"
205+
if call.image_entities and len(call.image_entities) > 0:
206+
camera_name = call.image_entities[0]
207+
title = "Motion detected near " + camera_name
208+
elif call.video_paths and len(call.video_paths) > 0:
209+
camera_name = call.video_paths[0].split(
210+
"/")[-1].replace(".mp4", "")
211+
title = "Motion detected in " + camera_name
212+
else:
213+
camera_name = ""
214+
title = "Motion detected"
210215

211-
if "response_text" not in response:
212-
raise ValueError("response_text is missing in the response")
216+
if "title" in response:
217+
title = response.get("title")
213218

214219
await semantic_index.remember(
215220
start=start,
216221
end=dt_util.now() + timedelta(minutes=1),
217222
label=title,
218-
camera_name=camera_name,
219-
summary=response["response_text"]
223+
summary=response["response_text"],
224+
key_frame=key_frame,
225+
camera_name=camera_name
220226
)
221227

222228

@@ -337,12 +343,17 @@ async def image_analyzer(data_call):
337343
image_paths=call.image_paths,
338344
target_width=call.target_width,
339345
include_filename=call.include_filename,
340-
expose_images=call.expose_images
346+
expose_images=call.expose_images,
347+
expose_images_persist=call.expose_images_persist
341348
)
342349

343350
# Validate configuration, input data and make the call
344351
response = await request.call(call)
345-
await _remember(hass, call, start, response)
352+
await _remember(hass=hass,
353+
call=call,
354+
start=start,
355+
response=response,
356+
key_frame=processor.key_frame)
346357
return response
347358

348359
async def video_analyzer(data_call):
@@ -368,7 +379,11 @@ async def video_analyzer(data_call):
368379
frigate_retry_seconds=call.frigate_retry_seconds
369380
)
370381
response = await request.call(call)
371-
await _remember(hass, call, start, response)
382+
await _remember(hass=hass,
383+
call=call,
384+
start=start,
385+
response=response,
386+
key_frame=processor.key_frame)
372387
return response
373388

374389
async def stream_analyzer(data_call):
@@ -382,16 +397,22 @@ async def stream_analyzer(data_call):
382397
temperature=call.temperature,
383398
)
384399
processor = MediaProcessor(hass, request)
400+
385401
request = await processor.add_streams(image_entities=call.image_entities,
386402
duration=call.duration,
387403
max_frames=call.max_frames,
388404
target_width=call.target_width,
389405
include_filename=call.include_filename,
390-
expose_images=call.expose_images
406+
expose_images=call.expose_images,
407+
expose_images_persist=call.expose_images_persist
391408
)
392409

393410
response = await request.call(call)
394-
await _remember(hass, call, start, response)
411+
await _remember(hass=hass,
412+
call=call,
413+
start=start,
414+
response=response,
415+
key_frame=processor.key_frame)
395416
return response
396417

397418
async def data_analyzer(data_call):
496 Bytes
Binary file not shown.
960 Bytes
Binary file not shown.
2.44 KB
Binary file not shown.
350 Bytes
Binary file not shown.
1.4 KB
Binary file not shown.
824 Bytes
Binary file not shown.

custom_components/llmvision/calendar.py

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,7 @@ class SemanticIndex(CalendarEntity):
2626
"""Representation of a Calendar."""
2727

2828
def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry):
29-
"""Initialize the calendar."""
29+
"""Initialize the calendar"""
3030
self.hass = hass
3131
self._attr_name = config_entry.title
3232
self._attr_unique_id = config_entry.entry_id
@@ -35,16 +35,18 @@ def __init__(self, hass: HomeAssistant, config_entry: ConfigEntry):
3535
self._attr_unique_id).get(CONF_RETENTION_TIME)
3636
self._current_event = None
3737
self._attr_supported_features = (CalendarEntityFeature.DELETE_EVENT)
38+
3839
# Path to the JSON file where events are stored
3940
self._file_path = os.path.join(
4041
self.hass.config.path("llmvision"), "events.json"
4142
)
43+
4244
# Ensure the directory exists
4345
os.makedirs(os.path.dirname(self._file_path), exist_ok=True)
4446
self.hass.loop.create_task(self.async_update())
45-
47+
4648
def _ensure_datetime(self, dt):
47-
"""Ensure the input is a datetime.datetime object."""
49+
"""Ensure the input is a datetime.datetime object"""
4850
if isinstance(dt, datetime.date) and not isinstance(dt, datetime.datetime):
4951
dt = datetime.datetime.combine(dt, datetime.datetime.min.time())
5052
if dt.tzinfo is None:
@@ -57,7 +59,7 @@ async def async_get_events(
5759
start_date: datetime.datetime,
5860
end_date: datetime.datetime,
5961
) -> list[CalendarEvent]:
60-
"""Return calendar events within a datetime range."""
62+
"""Return calendar events within a datetime range"""
6163
events = []
6264

6365
# Ensure start_date and end_date are datetime.datetime objects and timezone-aware
@@ -75,18 +77,23 @@ async def async_get_events(
7577

7678
@property
7779
def extra_state_attributes(self):
78-
"""Return the state attributes."""
80+
"""Return the state attributes"""
7981
return {
8082
"events": [event.summary for event in self._events],
83+
"starts": [event.start for event in self._events],
84+
"ends": [event.end for event in self._events],
85+
"summaries": [event.summary for event in self._events],
86+
"key_frames": [event.location.split(",")[0] for event in self._events],
87+
"camera_names": [event.location.split(",")[1] if len(event.location.split(",")) > 1 else "" for event in self._events],
8188
}
8289

8390
@property
8491
def event(self):
85-
"""Return the current event."""
92+
"""Return the current event"""
8693
return self._current_event
8794

8895
async def async_create_event(self, **kwargs: any) -> None:
89-
"""Add a new event to calendar."""
96+
"""Add a new event to calendar"""
9097
await self.async_update()
9198
dtstart = kwargs[EVENT_START]
9299
dtend = kwargs[EVENT_END]
@@ -127,7 +134,7 @@ async def async_delete_event(
127134
await self._save_events()
128135

129136
async def async_update(self) -> None:
130-
"""Load events from the JSON file."""
137+
"""Load events from JSON"""
131138
def read_from_file():
132139
if os.path.exists(self._file_path):
133140
with open(self._file_path, 'r') as file:
@@ -142,18 +149,17 @@ def read_from_file():
142149
start=dt_util.as_local(dt_util.parse_datetime(event["start"])),
143150
end=dt_util.as_local(dt_util.parse_datetime(event["end"])),
144151
description=event.get("description"),
145-
location=event.get("location"),
152+
location=event.get("location")
146153
)
147154
for event in events_data
148155
]
149-
# _LOGGER.info(f"events: {self._events}")
150156

151157
async def _save_events(self) -> None:
152-
"""Save events to the JSON file."""
158+
"""Save events to JSON"""
153159
# Delete events outside of retention time window
154160
now = datetime.datetime.now()
155161
cutoff_date = now - datetime.timedelta(days=self._retention_time)
156-
162+
157163
if self._retention_time != 0:
158164
_LOGGER.info(f"Deleting events before {cutoff_date}")
159165

@@ -164,7 +170,7 @@ async def _save_events(self) -> None:
164170
"start": dt_util.as_local(self._ensure_datetime(event.start)).isoformat(),
165171
"end": dt_util.as_local(self._ensure_datetime(event.end)).isoformat(),
166172
"description": event.description,
167-
"location": event.location,
173+
"location": event.location
168174
}
169175
for event in self._events
170176
if dt_util.as_local(self._ensure_datetime(event.end)) >= self._ensure_datetime(cutoff_date) or self._retention_time == 0
@@ -176,13 +182,13 @@ def write_to_file():
176182

177183
await self.hass.loop.run_in_executor(None, write_to_file)
178184

179-
async def remember(self, start, end, label, camera_name, summary):
180-
"""Remember the event."""
185+
async def remember(self, start, end, label, key_frame, summary, camera_name=""):
186+
"""Remember the event"""
181187
await self.async_create_event(
182188
dtstart=start,
183189
dtend=end,
184190
summary=label,
185-
location=camera_name,
191+
location=key_frame + "," + camera_name,
186192
description=summary,
187193
)
188194

0 commit comments

Comments
 (0)