Files
ha-text-ai/custom_components/ha_text_ai/config_flow.py
T

262 lines
9.3 KiB
Python
Raw Normal View History

2024-11-25 02:03:29 +03:00
# config_flow.py
"""Config flow for HA text AI integration.
This module defines a configuration flow for the HA text AI integration in Home Assistant.
The flow guides the user through the setup process, including the selection of API provider,
configuration of API settings, and validation of the entered data. It ensures that the
configuration entries are unique and establish a valid connection to the AI API service.
"""
2024-11-23 23:42:33 +03:00
import logging
2024-11-22 01:34:47 +03:00
from typing import Any, Dict, Optional
2024-11-19 12:45:26 +03:00
2024-11-23 23:42:33 +03:00
import voluptuous as vol
2024-11-18 00:43:28 +03:00
from homeassistant import config_entries
2024-11-24 02:20:29 +03:00
from homeassistant.const import CONF_API_KEY, CONF_NAME
2024-11-18 00:43:28 +03:00
from homeassistant.core import callback
2024-11-19 23:30:28 +03:00
from homeassistant.data_entry_flow import FlowResult
2024-11-19 23:38:07 +03:00
from homeassistant.helpers.aiohttp_client import async_get_clientsession
2024-11-24 02:20:29 +03:00
from homeassistant.helpers import selector
2024-11-14 18:34:38 +03:00
2024-11-18 00:43:28 +03:00
from .const import (
DOMAIN,
CONF_MODEL,
CONF_TEMPERATURE,
CONF_MAX_TOKENS,
CONF_API_ENDPOINT,
CONF_REQUEST_INTERVAL,
2024-11-22 01:34:47 +03:00
CONF_API_PROVIDER,
API_PROVIDER_OPENAI,
API_PROVIDER_ANTHROPIC,
API_PROVIDERS,
2024-11-18 00:43:28 +03:00
DEFAULT_MODEL,
DEFAULT_TEMPERATURE,
DEFAULT_MAX_TOKENS,
DEFAULT_REQUEST_INTERVAL,
2024-11-22 01:34:47 +03:00
DEFAULT_OPENAI_ENDPOINT,
DEFAULT_ANTHROPIC_ENDPOINT,
2024-11-20 01:02:27 +03:00
MIN_TEMPERATURE,
MAX_TEMPERATURE,
MIN_MAX_TOKENS,
MAX_MAX_TOKENS,
MIN_REQUEST_INTERVAL,
2024-11-18 00:43:28 +03:00
)
2024-11-14 18:34:38 +03:00
2024-11-19 14:00:33 +03:00
_LOGGER = logging.getLogger(__name__)
2024-11-19 12:45:26 +03:00
class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
2024-11-18 00:43:28 +03:00
"""Handle a config flow for HA text AI."""
2024-11-14 18:34:38 +03:00
2024-11-18 00:43:28 +03:00
VERSION = 1
2024-11-14 18:34:38 +03:00
2024-11-23 02:21:10 +03:00
def __init__(self) -> None:
"""Initialize flow."""
2024-11-24 02:20:29 +03:00
self._errors = {}
self._data = {}
self._provider = None
2024-11-23 03:02:35 +03:00
2024-11-22 01:34:47 +03:00
async def async_step_user(self, user_input: Optional[Dict[str, Any]] = None) -> FlowResult:
2024-11-25 02:03:29 +03:00
"""Handle the initial step.
This step asks the user to select an API provider.
"""
2024-11-22 01:34:47 +03:00
if user_input is None:
2024-11-24 02:20:29 +03:00
return self.async_show_form(
step_id="user",
data_schema=vol.Schema({
vol.Required(CONF_API_PROVIDER): selector.SelectSelector(
selector.SelectSelectorConfig(
options=API_PROVIDERS,
translation_key="api_provider"
)
),
})
)
2024-11-14 18:34:38 +03:00
2024-11-24 02:20:29 +03:00
self._provider = user_input[CONF_API_PROVIDER]
return await self.async_step_provider()
2024-11-21 18:30:35 +03:00
2024-11-24 02:20:29 +03:00
async def async_step_provider(self, user_input: Optional[Dict[str, Any]] = None) -> FlowResult:
2024-11-25 02:03:29 +03:00
"""Handle provider configuration step.
This step collects additional configuration details
based on the chosen provider like API key, model, etc.
"""
2024-11-24 02:20:29 +03:00
if user_input is None:
default_endpoint = (
DEFAULT_OPENAI_ENDPOINT if self._provider == API_PROVIDER_OPENAI
else DEFAULT_ANTHROPIC_ENDPOINT
)
2024-11-19 15:10:06 +03:00
2024-11-24 02:20:29 +03:00
suggested_name = f"HA Text AI {len(self._async_current_entries()) + 1}"
2024-11-14 18:34:38 +03:00
2024-11-24 02:20:29 +03:00
return self.async_show_form(
step_id="provider",
data_schema=vol.Schema({
vol.Required(CONF_NAME, default=suggested_name): str,
vol.Required(CONF_API_KEY): str,
vol.Required(CONF_MODEL, default=DEFAULT_MODEL): str,
vol.Required(CONF_API_ENDPOINT, default=default_endpoint): str,
vol.Optional(CONF_TEMPERATURE, default=DEFAULT_TEMPERATURE): vol.All(
vol.Coerce(float),
vol.Range(min=MIN_TEMPERATURE, max=MAX_TEMPERATURE)
),
vol.Optional(CONF_MAX_TOKENS, default=DEFAULT_MAX_TOKENS): vol.All(
vol.Coerce(int),
vol.Range(min=MIN_MAX_TOKENS, max=MAX_MAX_TOKENS)
),
vol.Optional(CONF_REQUEST_INTERVAL, default=DEFAULT_REQUEST_INTERVAL): vol.All(
vol.Coerce(float),
vol.Range(min=MIN_REQUEST_INTERVAL)
),
}),
errors=self._errors
)
instance_name = user_input[CONF_NAME]
await self._async_validate_name(instance_name)
if self._errors:
return await self.async_step_provider()
if not await self._async_validate_api(user_input):
return await self.async_step_provider()
return await self._create_entry(user_input)
async def _async_validate_name(self, name: str) -> bool:
2024-11-25 02:03:29 +03:00
"""Validate that the name is unique.
Ensure no existing configuration entry has the same name.
"""
2024-11-24 02:20:29 +03:00
for entry in self._async_current_entries():
if entry.data.get(CONF_NAME) == name:
self._errors["name"] = "name_exists"
return False
return True
async def _async_validate_api(self, user_input: Dict[str, Any]) -> bool:
2024-11-25 02:03:29 +03:00
"""Validate API connection.
Test the API connection to ensure the provided API details are correct.
"""
2024-11-23 01:09:38 +03:00
try:
session = async_get_clientsession(self.hass)
2024-11-23 02:21:10 +03:00
headers = self._get_api_headers(user_input)
2024-11-24 02:20:29 +03:00
endpoint = user_input[CONF_API_ENDPOINT].rstrip('/')
2024-11-23 01:09:38 +03:00
2024-11-24 02:20:29 +03:00
check_url = (
f"{endpoint}/v1/models" if self._provider == API_PROVIDER_ANTHROPIC
else f"{endpoint}/models"
)
2024-11-23 01:09:38 +03:00
2024-11-24 02:20:29 +03:00
async with session.get(check_url, headers=headers) as response:
if response.status == 401:
self._errors["base"] = "invalid_auth"
return False
elif response.status not in [200, 404]:
self._errors["base"] = "cannot_connect"
return False
return True
2024-11-23 01:09:38 +03:00
2024-11-24 02:20:29 +03:00
except Exception as err:
_LOGGER.error("API validation error: %s", str(err))
self._errors["base"] = "cannot_connect"
return False
2024-11-22 12:02:26 +03:00
2024-11-24 02:20:29 +03:00
def _get_api_headers(self, user_input: Dict[str, Any]) -> Dict[str, str]:
2024-11-25 02:03:29 +03:00
"""Get API headers based on provider.
Returns appropriate headers for API requests depending on the provider.
"""
2024-11-24 02:20:29 +03:00
api_key = user_input[CONF_API_KEY]
if self._provider == API_PROVIDER_ANTHROPIC:
2024-11-23 02:21:10 +03:00
return {
2024-11-24 02:20:29 +03:00
"x-api-key": api_key,
"anthropic-version": "2023-06-01",
"Content-Type": "application/json"
2024-11-23 02:21:10 +03:00
}
return {
2024-11-24 02:20:29 +03:00
"Authorization": f"Bearer {api_key}",
2024-11-23 02:21:10 +03:00
"Content-Type": "application/json"
}
2024-11-24 02:20:29 +03:00
async def _create_entry(self, user_input: Dict[str, Any]) -> FlowResult:
2024-11-25 02:03:29 +03:00
"""Create the config entry.
Constructs and adds the configuration entry to Home Assistant.
"""
2024-11-24 02:20:29 +03:00
instance_name = user_input[CONF_NAME]
unique_id = f"{DOMAIN}_{instance_name}_{self._provider}".lower().replace(" ", "_")
return self.async_create_entry(
title=instance_name,
data={
CONF_API_PROVIDER: self._provider,
CONF_NAME: instance_name,
**user_input,
"unique_id": unique_id
}
2024-11-23 01:09:38 +03:00
)
2024-11-23 00:51:57 +03:00
2024-11-24 02:20:29 +03:00
@staticmethod
@callback
def async_get_options_flow(config_entry: config_entries.ConfigEntry) -> config_entries.OptionsFlow:
2024-11-25 02:03:29 +03:00
"""Get the options flow for this handler.
Provides access to configuration options after initial setup.
"""
2024-11-24 02:20:29 +03:00
return OptionsFlowHandler(config_entry)
2024-11-23 03:02:35 +03:00
2024-11-18 00:43:28 +03:00
class OptionsFlowHandler(config_entries.OptionsFlow):
2024-11-23 02:21:10 +03:00
"""Handle options flow."""
2024-11-14 18:34:38 +03:00
2024-11-19 12:45:26 +03:00
def __init__(self, config_entry: config_entries.ConfigEntry) -> None:
2024-11-25 02:03:29 +03:00
"""Initialize options flow.
Prepare for managing configurable options of the integration.
"""
2024-11-18 00:43:28 +03:00
self.config_entry = config_entry
2024-11-14 18:34:38 +03:00
2024-11-22 01:34:47 +03:00
async def async_step_init(self, user_input: Optional[Dict[str, Any]] = None) -> FlowResult:
2024-11-25 02:03:29 +03:00
"""Manage the options.
Allows modification of configurable parameters post initial setup.
"""
2024-11-18 00:43:28 +03:00
if user_input is not None:
2024-11-22 01:34:47 +03:00
return self.async_create_entry(title="", data=user_input)
2024-11-24 02:20:29 +03:00
current_data = {**self.config_entry.data, **self.config_entry.options}
2024-11-21 18:30:35 +03:00
return self.async_show_form(
step_id="init",
2024-11-22 01:34:47 +03:00
data_schema=vol.Schema({
2024-11-24 02:20:29 +03:00
vol.Optional(
CONF_MODEL,
default=current_data.get(CONF_MODEL, DEFAULT_MODEL)
): str,
2024-11-22 01:34:47 +03:00
vol.Optional(
CONF_TEMPERATURE,
default=current_data.get(CONF_TEMPERATURE, DEFAULT_TEMPERATURE)
): vol.All(
vol.Coerce(float),
vol.Range(min=MIN_TEMPERATURE, max=MAX_TEMPERATURE)
),
vol.Optional(
CONF_MAX_TOKENS,
default=current_data.get(CONF_MAX_TOKENS, DEFAULT_MAX_TOKENS)
): vol.All(
vol.Coerce(int),
vol.Range(min=MIN_MAX_TOKENS, max=MAX_MAX_TOKENS)
),
vol.Optional(
CONF_REQUEST_INTERVAL,
default=current_data.get(CONF_REQUEST_INTERVAL, DEFAULT_REQUEST_INTERVAL)
): vol.All(
vol.Coerce(float),
vol.Range(min=MIN_REQUEST_INTERVAL)
),
})
2024-11-21 18:30:35 +03:00
)