Skip to content

Commit e420387

Browse files
authored
Merge branch 'dev' into ai-task-structured-data
2 parents ff58c4e + 1fc624c commit e420387

File tree

27 files changed

+755
-75
lines changed

27 files changed

+755
-75
lines changed

homeassistant/components/androidtv_remote/config_flow.py

Lines changed: 27 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616

1717
from homeassistant.config_entries import (
1818
SOURCE_REAUTH,
19+
SOURCE_RECONFIGURE,
1920
ConfigFlow,
2021
ConfigFlowResult,
2122
OptionsFlow,
@@ -40,12 +41,6 @@
4041
CONF_APP_DELETE = "app_delete"
4142
CONF_APP_ID = "app_id"
4243

43-
STEP_USER_DATA_SCHEMA = vol.Schema(
44-
{
45-
vol.Required("host"): str,
46-
}
47-
)
48-
4944
STEP_PAIR_DATA_SCHEMA = vol.Schema(
5045
{
5146
vol.Required("pin"): str,
@@ -66,7 +61,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
6661
async def async_step_user(
6762
self, user_input: dict[str, Any] | None = None
6863
) -> ConfigFlowResult:
69-
"""Handle the initial step."""
64+
"""Handle the initial and reconfigure step."""
7065
errors: dict[str, str] = {}
7166
if user_input is not None:
7267
self.host = user_input[CONF_HOST]
@@ -75,15 +70,32 @@ async def async_step_user(
7570
await api.async_generate_cert_if_missing()
7671
self.name, self.mac = await api.async_get_name_and_mac()
7772
await self.async_set_unique_id(format_mac(self.mac))
73+
if self.source == SOURCE_RECONFIGURE:
74+
self._abort_if_unique_id_mismatch()
75+
return self.async_update_reload_and_abort(
76+
self._get_reconfigure_entry(),
77+
data={
78+
CONF_HOST: self.host,
79+
CONF_NAME: self.name,
80+
CONF_MAC: self.mac,
81+
},
82+
)
7883
self._abort_if_unique_id_configured(updates={CONF_HOST: self.host})
7984
return await self._async_start_pair()
8085
except (CannotConnect, ConnectionClosed):
8186
# Likely invalid IP address or device is network unreachable. Stay
8287
# in the user step allowing the user to enter a different host.
8388
errors["base"] = "cannot_connect"
89+
else:
90+
user_input = {}
91+
default_host = user_input.get(CONF_HOST, vol.UNDEFINED)
92+
if self.source == SOURCE_RECONFIGURE:
93+
default_host = self._get_reconfigure_entry().data[CONF_HOST]
8494
return self.async_show_form(
85-
step_id="user",
86-
data_schema=STEP_USER_DATA_SCHEMA,
95+
step_id="reconfigure" if self.source == SOURCE_RECONFIGURE else "user",
96+
data_schema=vol.Schema(
97+
{vol.Required(CONF_HOST, default=default_host): str}
98+
),
8799
errors=errors,
88100
)
89101

@@ -216,6 +228,12 @@ async def async_step_reauth_confirm(
216228
errors=errors,
217229
)
218230

231+
async def async_step_reconfigure(
232+
self, user_input: dict[str, Any] | None = None
233+
) -> ConfigFlowResult:
234+
"""Handle reconfiguration."""
235+
return await self.async_step_user(user_input)
236+
219237
@staticmethod
220238
@callback
221239
def async_get_options_flow(

homeassistant/components/androidtv_remote/strings.json

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,15 @@
1111
"host": "The hostname or IP address of the Android TV device."
1212
}
1313
},
14+
"reconfigure": {
15+
"description": "Update the IP address of this previously configured Android TV device.",
16+
"data": {
17+
"host": "[%key:common::config_flow::data::host%]"
18+
},
19+
"data_description": {
20+
"host": "The hostname or IP address of the Android TV device."
21+
}
22+
},
1423
"zeroconf_confirm": {
1524
"title": "Discovered Android TV",
1625
"description": "Do you want to add the Android TV ({name}) to Home Assistant? It will turn on and a pairing code will be displayed on it that you will need to enter in the next screen."
@@ -38,7 +47,9 @@
3847
"abort": {
3948
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
4049
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
41-
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
50+
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
51+
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
52+
"unique_id_mismatch": "Please ensure you reconfigure against the same device."
4253
}
4354
},
4455
"options": {

homeassistant/components/enphase_envoy/__init__.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: EnphaseConfigEntry) ->
6363
coordinator = entry.runtime_data
6464
coordinator.async_cancel_token_refresh()
6565
coordinator.async_cancel_firmware_refresh()
66+
coordinator.async_cancel_mac_verification()
6667
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
6768

6869

homeassistant/components/enphase_envoy/manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
"iot_class": "local_polling",
88
"loggers": ["pyenphase"],
99
"quality_scale": "platinum",
10-
"requirements": ["pyenphase==2.1.0"],
10+
"requirements": ["pyenphase==2.2.0"],
1111
"zeroconf": [
1212
{
1313
"type": "_enphase-envoy._tcp.local."

homeassistant/components/google_generative_ai_conversation/__init__.py

Lines changed: 20 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
import asyncio
6+
from functools import partial
67
import mimetypes
78
from pathlib import Path
89
from types import MappingProxyType
@@ -37,11 +38,13 @@
3738

3839
from .const import (
3940
CONF_PROMPT,
41+
DEFAULT_AI_TASK_NAME,
4042
DEFAULT_TITLE,
4143
DEFAULT_TTS_NAME,
4244
DOMAIN,
4345
FILE_POLLING_INTERVAL_SECONDS,
4446
LOGGER,
47+
RECOMMENDED_AI_TASK_OPTIONS,
4548
RECOMMENDED_CHAT_MODEL,
4649
RECOMMENDED_TTS_OPTIONS,
4750
TIMEOUT_MILLIS,
@@ -53,6 +56,7 @@
5356

5457
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
5558
PLATFORMS = (
59+
Platform.AI_TASK,
5660
Platform.CONVERSATION,
5761
Platform.TTS,
5862
)
@@ -187,11 +191,9 @@ async def async_setup_entry(
187191
"""Set up Google Generative AI Conversation from a config entry."""
188192

189193
try:
190-
191-
def _init_client() -> Client:
192-
return Client(api_key=entry.data[CONF_API_KEY])
193-
194-
client = await hass.async_add_executor_job(_init_client)
194+
client = await hass.async_add_executor_job(
195+
partial(Client, api_key=entry.data[CONF_API_KEY])
196+
)
195197
await client.aio.models.get(
196198
model=RECOMMENDED_CHAT_MODEL,
197199
config={"http_options": {"timeout": TIMEOUT_MILLIS}},
@@ -350,6 +352,19 @@ async def async_migrate_entry(
350352

351353
hass.config_entries.async_update_entry(entry, minor_version=2)
352354

355+
if entry.version == 2 and entry.minor_version == 2:
356+
# Add AI Task subentry with default options
357+
hass.config_entries.async_add_subentry(
358+
entry,
359+
ConfigSubentry(
360+
data=MappingProxyType(RECOMMENDED_AI_TASK_OPTIONS),
361+
subentry_type="ai_task_data",
362+
title=DEFAULT_AI_TASK_NAME,
363+
unique_id=None,
364+
),
365+
)
366+
hass.config_entries.async_update_entry(entry, minor_version=3)
367+
353368
LOGGER.debug(
354369
"Migration to version %s:%s successful", entry.version, entry.minor_version
355370
)
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
"""AI Task integration for Google Generative AI Conversation."""
2+
3+
from __future__ import annotations
4+
5+
from homeassistant.components import ai_task, conversation
6+
from homeassistant.config_entries import ConfigEntry
7+
from homeassistant.core import HomeAssistant
8+
from homeassistant.exceptions import HomeAssistantError
9+
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
10+
11+
from .const import LOGGER
12+
from .entity import ERROR_GETTING_RESPONSE, GoogleGenerativeAILLMBaseEntity
13+
14+
15+
async def async_setup_entry(
16+
hass: HomeAssistant,
17+
config_entry: ConfigEntry,
18+
async_add_entities: AddConfigEntryEntitiesCallback,
19+
) -> None:
20+
"""Set up AI Task entities."""
21+
for subentry in config_entry.subentries.values():
22+
if subentry.subentry_type != "ai_task_data":
23+
continue
24+
25+
async_add_entities(
26+
[GoogleGenerativeAITaskEntity(config_entry, subentry)],
27+
config_subentry_id=subentry.subentry_id,
28+
)
29+
30+
31+
class GoogleGenerativeAITaskEntity(
32+
ai_task.AITaskEntity,
33+
GoogleGenerativeAILLMBaseEntity,
34+
):
35+
"""Google Generative AI AI Task entity."""
36+
37+
_attr_supported_features = ai_task.AITaskEntityFeature.GENERATE_DATA
38+
39+
async def _async_generate_data(
40+
self,
41+
task: ai_task.GenDataTask,
42+
chat_log: conversation.ChatLog,
43+
) -> ai_task.GenDataTaskResult:
44+
"""Handle a generate data task."""
45+
await self._async_handle_chat_log(chat_log)
46+
47+
if not isinstance(chat_log.content[-1], conversation.AssistantContent):
48+
LOGGER.error(
49+
"Last content in chat log is not an AssistantContent: %s. This could be due to the model not returning a valid response",
50+
chat_log.content[-1],
51+
)
52+
raise HomeAssistantError(ERROR_GETTING_RESPONSE)
53+
54+
return ai_task.GenDataTaskResult(
55+
conversation_id=chat_log.conversation_id,
56+
data=chat_log.content[-1].content or "",
57+
)

homeassistant/components/google_generative_ai_conversation/config_flow.py

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
from __future__ import annotations
44

55
from collections.abc import Mapping
6+
from functools import partial
67
import logging
78
from typing import Any, cast
89

@@ -46,10 +47,12 @@
4647
CONF_TOP_K,
4748
CONF_TOP_P,
4849
CONF_USE_GOOGLE_SEARCH_TOOL,
50+
DEFAULT_AI_TASK_NAME,
4951
DEFAULT_CONVERSATION_NAME,
5052
DEFAULT_TITLE,
5153
DEFAULT_TTS_NAME,
5254
DOMAIN,
55+
RECOMMENDED_AI_TASK_OPTIONS,
5356
RECOMMENDED_CHAT_MODEL,
5457
RECOMMENDED_CONVERSATION_OPTIONS,
5558
RECOMMENDED_HARM_BLOCK_THRESHOLD,
@@ -72,12 +75,14 @@
7275
)
7376

7477

75-
async def validate_input(data: dict[str, Any]) -> None:
78+
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
7679
"""Validate the user input allows us to connect.
7780
7881
Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user.
7982
"""
80-
client = genai.Client(api_key=data[CONF_API_KEY])
83+
client = await hass.async_add_executor_job(
84+
partial(genai.Client, api_key=data[CONF_API_KEY])
85+
)
8186
await client.aio.models.list(
8287
config={
8388
"http_options": {
@@ -92,7 +97,7 @@ class GoogleGenerativeAIConfigFlow(ConfigFlow, domain=DOMAIN):
9297
"""Handle a config flow for Google Generative AI Conversation."""
9398

9499
VERSION = 2
95-
MINOR_VERSION = 2
100+
MINOR_VERSION = 3
96101

97102
async def async_step_api(
98103
self, user_input: dict[str, Any] | None = None
@@ -102,7 +107,7 @@ async def async_step_api(
102107
if user_input is not None:
103108
self._async_abort_entries_match(user_input)
104109
try:
105-
await validate_input(user_input)
110+
await validate_input(self.hass, user_input)
106111
except (APIError, Timeout) as err:
107112
if isinstance(err, ClientError) and "API_KEY_INVALID" in str(err):
108113
errors["base"] = "invalid_auth"
@@ -133,6 +138,12 @@ async def async_step_api(
133138
"title": DEFAULT_TTS_NAME,
134139
"unique_id": None,
135140
},
141+
{
142+
"subentry_type": "ai_task_data",
143+
"data": RECOMMENDED_AI_TASK_OPTIONS,
144+
"title": DEFAULT_AI_TASK_NAME,
145+
"unique_id": None,
146+
},
136147
],
137148
)
138149
return self.async_show_form(
@@ -181,6 +192,7 @@ def async_get_supported_subentry_types(
181192
return {
182193
"conversation": LLMSubentryFlowHandler,
183194
"tts": LLMSubentryFlowHandler,
195+
"ai_task_data": LLMSubentryFlowHandler,
184196
}
185197

186198

@@ -214,6 +226,8 @@ async def async_step_set_options(
214226
options: dict[str, Any]
215227
if self._subentry_type == "tts":
216228
options = RECOMMENDED_TTS_OPTIONS.copy()
229+
elif self._subentry_type == "ai_task_data":
230+
options = RECOMMENDED_AI_TASK_OPTIONS.copy()
217231
else:
218232
options = RECOMMENDED_CONVERSATION_OPTIONS.copy()
219233
else:
@@ -288,6 +302,8 @@ async def google_generative_ai_config_option_schema(
288302
default_name = options[CONF_NAME]
289303
elif subentry_type == "tts":
290304
default_name = DEFAULT_TTS_NAME
305+
elif subentry_type == "ai_task_data":
306+
default_name = DEFAULT_AI_TASK_NAME
291307
else:
292308
default_name = DEFAULT_CONVERSATION_NAME
293309
schema: dict[vol.Required | vol.Optional, Any] = {
@@ -315,6 +331,7 @@ async def google_generative_ai_config_option_schema(
315331
),
316332
}
317333
)
334+
318335
schema.update(
319336
{
320337
vol.Required(
@@ -443,4 +460,5 @@ async def google_generative_ai_config_option_schema(
443460
): bool,
444461
}
445462
)
463+
446464
return schema

homeassistant/components/google_generative_ai_conversation/const.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212

1313
DEFAULT_CONVERSATION_NAME = "Google AI Conversation"
1414
DEFAULT_TTS_NAME = "Google AI TTS"
15+
DEFAULT_AI_TASK_NAME = "Google AI Task"
1516

1617
CONF_RECOMMENDED = "recommended"
1718
CONF_CHAT_MODEL = "chat_model"
@@ -35,6 +36,7 @@
3536

3637
TIMEOUT_MILLIS = 10000
3738
FILE_POLLING_INTERVAL_SECONDS = 0.05
39+
3840
RECOMMENDED_CONVERSATION_OPTIONS = {
3941
CONF_PROMPT: llm.DEFAULT_INSTRUCTIONS_PROMPT,
4042
CONF_LLM_HASS_API: [llm.LLM_API_ASSIST],
@@ -44,3 +46,7 @@
4446
RECOMMENDED_TTS_OPTIONS = {
4547
CONF_RECOMMENDED: True,
4648
}
49+
50+
RECOMMENDED_AI_TASK_OPTIONS = {
51+
CONF_RECOMMENDED: True,
52+
}

0 commit comments

Comments
 (0)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy