Compare commits

..
4 Commits
Author SHA1 Message Date
SMKRV d899144149 Release v1.0.3 2024-11-19 14:00:33 +03:00
SMKRV 5d49b4a40b Release v1.0.2 2024-11-19 13:16:14 +03:00
SMKRV 1a84727cf1 Release v1.0.2 2024-11-19 13:14:00 +03:00
SMKRV d3c7e25202 Release v1.0.2 2024-11-19 13:12:41 +03:00
11 changed files with 287 additions and 71 deletions
+11 -7
View File
@@ -23,10 +23,13 @@ from .const import (
CONF_API_ENDPOINT, CONF_API_ENDPOINT,
CONF_REQUEST_INTERVAL, CONF_REQUEST_INTERVAL,
) )
from .coordinator import HATextAICoordinator
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
def get_coordinator():
"""Get coordinator class with lazy import to avoid circular deps."""
from .coordinator import HATextAICoordinator
return HATextAICoordinator
CONFIG_SCHEMA = vol.Schema( CONFIG_SCHEMA = vol.Schema(
{ {
@@ -56,7 +59,6 @@ async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool:
coordinator = next(iter(hass.data[DOMAIN].values())) coordinator = next(iter(hass.data[DOMAIN].values()))
question = call.data["question"] question = call.data["question"]
original_params = { original_params = {
"model": coordinator.model, "model": coordinator.model,
"temperature": coordinator.temperature, "temperature": coordinator.temperature,
@@ -64,7 +66,6 @@ async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool:
} }
try: try:
if "model" in call.data: if "model" in call.data:
coordinator.model = call.data["model"] coordinator.model = call.data["model"]
if "temperature" in call.data: if "temperature" in call.data:
@@ -77,7 +78,6 @@ async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool:
_LOGGER.error("Error asking question: %s", str(ex)) _LOGGER.error("Error asking question: %s", str(ex))
raise HomeAssistantError(f"Failed to ask question: {str(ex)}") from ex raise HomeAssistantError(f"Failed to ask question: {str(ex)}") from ex
finally: finally:
coordinator.model = original_params["model"] coordinator.model = original_params["model"]
coordinator.temperature = original_params["temperature"] coordinator.temperature = original_params["temperature"]
coordinator.max_tokens = original_params["max_tokens"] coordinator.max_tokens = original_params["max_tokens"]
@@ -118,7 +118,6 @@ async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool:
coordinator = next(iter(hass.data[DOMAIN].values())) coordinator = next(iter(hass.data[DOMAIN].values()))
coordinator.system_prompt = call.data["prompt"] coordinator.system_prompt = call.data["prompt"]
hass.services.async_register( hass.services.async_register(
DOMAIN, DOMAIN,
SERVICE_ASK_QUESTION, SERVICE_ASK_QUESTION,
@@ -166,6 +165,7 @@ async def async_setup(hass: HomeAssistant, config: dict[str, Any]) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up HA text AI from a config entry.""" """Set up HA text AI from a config entry."""
HATextAICoordinator = get_coordinator()
try: try:
coordinator = HATextAICoordinator( coordinator = HATextAICoordinator(
hass, hass,
@@ -186,11 +186,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry.""" """Unload a config entry."""
if entry.entry_id not in hass.data.get(DOMAIN, {}):
return True
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok: if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id) hass.data[DOMAIN].pop(entry.entry_id)
# Remove services only if this was the last instance
if not hass.data[DOMAIN]: if not hass.data[DOMAIN]:
services = [ services = [
SERVICE_ASK_QUESTION, SERVICE_ASK_QUESTION,
@@ -199,7 +202,8 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
SERVICE_SET_SYSTEM_PROMPT SERVICE_SET_SYSTEM_PROMPT
] ]
for service in services: for service in services:
if service in hass.services.async_services().get(DOMAIN, {}): if DOMAIN in hass.services.async_services() and \
service in hass.services.async_services()[DOMAIN]:
hass.services.async_remove(DOMAIN, service) hass.services.async_remove(DOMAIN, service)
return unload_ok return unload_ok
+34 -16
View File
@@ -22,6 +22,9 @@ from .const import (
DEFAULT_REQUEST_INTERVAL, DEFAULT_REQUEST_INTERVAL,
) )
import logging
_LOGGER = logging.getLogger(__name__)
STEP_USER_DATA_SCHEMA = vol.Schema({ STEP_USER_DATA_SCHEMA = vol.Schema({
vol.Required(CONF_API_KEY): str, vol.Required(CONF_API_KEY): str,
vol.Optional(CONF_MODEL, default=DEFAULT_MODEL): str, vol.Optional(CONF_MODEL, default=DEFAULT_MODEL): str,
@@ -54,35 +57,50 @@ class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
if user_input is not None: if user_input is not None:
try: try:
# Create OpenAI client
client = openai.OpenAI( client = openai.OpenAI(
api_key=user_input[CONF_API_KEY], api_key=user_input[CONF_API_KEY],
base_url=user_input.get(CONF_API_ENDPOINT, DEFAULT_API_ENDPOINT) base_url=user_input.get(CONF_API_ENDPOINT, DEFAULT_API_ENDPOINT)
) )
await self.hass.async_add_executor_job(
client.models.list
)
# Verify API connection and model availability
models = await self.hass.async_add_executor_job(client.models.list)
model_ids = [model.id for model in models.data]
await self.async_set_unique_id(user_input[CONF_API_KEY]) if user_input[CONF_MODEL] not in model_ids:
self._abort_if_unique_id_configured() _LOGGER.warning(
"Selected model %s not found in available models: %s",
user_input[CONF_MODEL],
", ".join(model_ids)
)
errors["base"] = "invalid_model"
else:
await self.async_set_unique_id(user_input[CONF_API_KEY])
self._abort_if_unique_id_configured()
return self.async_create_entry( return self.async_create_entry(
title="HA text AI", title="HA text AI",
data=user_input data=user_input
) )
except openai.AuthenticationError: except openai.AuthenticationError as err:
_LOGGER.error("Authentication failed: %s", str(err))
errors["base"] = "invalid_auth" errors["base"] = "invalid_auth"
except openai.APIError: except openai.APIError as err:
_LOGGER.error("API connection failed: %s", str(err))
errors["base"] = "cannot_connect" errors["base"] = "cannot_connect"
except Exception: # pylint: disable=broad-except except Exception as err: # pylint: disable=broad-except
_LOGGER.exception("Unexpected error: %s", str(err))
errors["base"] = "unknown" errors["base"] = "unknown"
return self.async_show_form( return self.async_show_form(
step_id="user", step_id="user",
data_schema=STEP_USER_DATA_SCHEMA, data_schema=STEP_USER_DATA_SCHEMA,
errors=errors, errors=errors,
description_placeholders={
"default_model": DEFAULT_MODEL,
"default_endpoint": DEFAULT_API_ENDPOINT,
}
) )
@staticmethod @staticmethod
@@ -114,21 +132,21 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
default=self.config_entry.options.get( default=self.config_entry.options.get(
CONF_TEMPERATURE, DEFAULT_TEMPERATURE CONF_TEMPERATURE, DEFAULT_TEMPERATURE
), ),
description="Temperature for response generation (0-2)", description={"suggested_value": DEFAULT_TEMPERATURE},
): vol.All(vol.Coerce(float), vol.Range(min=0, max=2)), ): vol.All(vol.Coerce(float), vol.Range(min=0, max=2)),
vol.Optional( vol.Optional(
CONF_MAX_TOKENS, CONF_MAX_TOKENS,
default=self.config_entry.options.get( default=self.config_entry.options.get(
CONF_MAX_TOKENS, DEFAULT_MAX_TOKENS CONF_MAX_TOKENS, DEFAULT_MAX_TOKENS
), ),
description="Maximum tokens in response (1-4096)", description={"suggested_value": DEFAULT_MAX_TOKENS},
): vol.All(vol.Coerce(int), vol.Range(min=1, max=4096)), ): vol.All(vol.Coerce(int), vol.Range(min=1, max=4096)),
vol.Optional( vol.Optional(
CONF_REQUEST_INTERVAL, CONF_REQUEST_INTERVAL,
default=self.config_entry.options.get( default=self.config_entry.options.get(
CONF_REQUEST_INTERVAL, DEFAULT_REQUEST_INTERVAL CONF_REQUEST_INTERVAL, DEFAULT_REQUEST_INTERVAL
), ),
description="Minimum time between API requests (seconds)", description={"suggested_value": DEFAULT_REQUEST_INTERVAL},
): vol.All(vol.Coerce(float), vol.Range(min=0.1)), ): vol.All(vol.Coerce(float), vol.Range(min=0.1)),
}) })
+31
View File
@@ -43,11 +43,21 @@ SERVICE_SET_SYSTEM_PROMPT_DESCRIPTION: Final = "Set system prompt for AI model"
ATTR_QUESTION: Final = "question" ATTR_QUESTION: Final = "question"
ATTR_RESPONSE: Final = "response" ATTR_RESPONSE: Final = "response"
ATTR_LAST_UPDATED: Final = "last_updated" ATTR_LAST_UPDATED: Final = "last_updated"
ATTR_MODEL: Final = "model"
ATTR_TEMPERATURE: Final = "temperature"
ATTR_MAX_TOKENS: Final = "max_tokens"
ATTR_TOTAL_RESPONSES: Final = "total_responses"
ATTR_SYSTEM_PROMPT: Final = "system_prompt"
ATTR_RESPONSE_TIME: Final = "response_time"
# Error messages # Error messages
ERROR_INVALID_API_KEY: Final = "invalid_api_key" ERROR_INVALID_API_KEY: Final = "invalid_api_key"
ERROR_CANNOT_CONNECT: Final = "cannot_connect" ERROR_CANNOT_CONNECT: Final = "cannot_connect"
ERROR_UNKNOWN: Final = "unknown_error" ERROR_UNKNOWN: Final = "unknown_error"
ERROR_INVALID_MODEL: Final = "invalid_model"
ERROR_RATE_LIMIT: Final = "rate_limit_exceeded"
ERROR_CONTEXT_LENGTH: Final = "context_length_exceeded"
ERROR_API_ERROR: Final = "api_error"
# Configuration descriptions # Configuration descriptions
CONF_MODEL_DESCRIPTION: Final = "AI model to use for responses" CONF_MODEL_DESCRIPTION: Final = "AI model to use for responses"
@@ -56,6 +66,27 @@ CONF_MAX_TOKENS_DESCRIPTION: Final = "Maximum tokens in response (1-4096)"
CONF_API_ENDPOINT_DESCRIPTION: Final = "API endpoint URL" CONF_API_ENDPOINT_DESCRIPTION: Final = "API endpoint URL"
CONF_REQUEST_INTERVAL_DESCRIPTION: Final = "Minimum time between API requests (seconds)" CONF_REQUEST_INTERVAL_DESCRIPTION: Final = "Minimum time between API requests (seconds)"
# Entity attributes descriptions
ATTR_QUESTION_DESCRIPTION: Final = "Last question asked"
ATTR_RESPONSE_DESCRIPTION: Final = "Last response received"
ATTR_LAST_UPDATED_DESCRIPTION: Final = "Time of last update"
ATTR_MODEL_DESCRIPTION: Final = "Current AI model in use"
ATTR_TEMPERATURE_DESCRIPTION: Final = "Current temperature setting"
ATTR_MAX_TOKENS_DESCRIPTION: Final = "Current max tokens setting"
ATTR_TOTAL_RESPONSES_DESCRIPTION: Final = "Total number of responses"
ATTR_SYSTEM_PROMPT_DESCRIPTION: Final = "Current system prompt"
ATTR_RESPONSE_TIME_DESCRIPTION: Final = "Time taken for last response"
# Entity attributes # Entity attributes
ENTITY_NAME: Final = "HA Text AI" ENTITY_NAME: Final = "HA Text AI"
ENTITY_ICON: Final = "mdi:robot" ENTITY_ICON: Final = "mdi:robot"
# Translation keys
TRANSLATION_KEY_CONFIG: Final = "config"
TRANSLATION_KEY_OPTIONS: Final = "options"
TRANSLATION_KEY_ERROR: Final = "error"
# State attributes
STATE_READY: Final = "ready"
STATE_PROCESSING: Final = "processing"
STATE_ERROR: Final = "error"
@@ -3,7 +3,6 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN, PLATFORMS from .const import DOMAIN, PLATFORMS
from .coordinator import HATextAICoordinator
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up HA Text AI from a config entry.""" """Set up HA Text AI from a config entry."""
+5 -5
View File
@@ -1,14 +1,14 @@
{ {
"domain": "ha_text_ai", "domain": "ha_text_ai",
"name": "HA Text AI", "name": "HA Text AI",
"codeowners": ["@smkrv"],
"config_flow": true, "config_flow": true,
"dependencies": [],
"documentation": "https://github.com/smkrv/ha-text-ai/wiki", "documentation": "https://github.com/smkrv/ha-text-ai/wiki",
"iot_class": "cloud_polling",
"issue_tracker": "https://github.com/smkrv/ha-text-ai/issues", "issue_tracker": "https://github.com/smkrv/ha-text-ai/issues",
"requirements": ["openai>=1.0.0"], "requirements": ["openai>=1.0.0"],
"ssdp": [], "ssdp": [],
"zeroconf": [], "version": "1.0.3",
"dependencies": [], "zeroconf": []
"version": "1.0.1c",
"iot_class": "cloud_polling",
"codeowners": ["@smkrv"]
} }
+54 -10
View File
@@ -13,8 +13,18 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import dt as dt_util
from .const import DOMAIN, ATTR_QUESTION, ATTR_RESPONSE, ATTR_LAST_UPDATED from .const import (
DOMAIN,
ATTR_QUESTION,
ATTR_RESPONSE,
ATTR_LAST_UPDATED,
ATTR_MODEL,
ATTR_TEMPERATURE,
ATTR_MAX_TOKENS,
ATTR_TOTAL_RESPONSES,
)
from .coordinator import HATextAICoordinator from .coordinator import HATextAICoordinator
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -46,42 +56,71 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
self._config_entry = config_entry self._config_entry = config_entry
self._attr_unique_id = f"{config_entry.entry_id}" self._attr_unique_id = f"{config_entry.entry_id}"
self._attr_name = "Last Response" self._attr_name = "Last Response"
self._attr_suggested_display_precision = 0
@property @property
def state(self) -> StateType: def state(self) -> StateType:
"""Return the state of the sensor.""" """Return the state of the sensor."""
if not self.coordinator.data: if not self.coordinator.data or not self.coordinator.last_update_success_time:
return None return None
# Convert to local time
if isinstance(self.coordinator.last_update_success_time, datetime):
return dt_util.as_local(self.coordinator.last_update_success_time)
return self.coordinator.last_update_success_time return self.coordinator.last_update_success_time
@property @property
def extra_state_attributes(self) -> Optional[Dict[str, Any]]: def extra_state_attributes(self) -> Optional[Dict[str, Any]]:
"""Return entity specific state attributes.""" """Return entity specific state attributes."""
if not self.coordinator.data: if not self.coordinator.data:
return None return {
ATTR_TOTAL_RESPONSES: 0,
ATTR_MODEL: self.coordinator.model,
ATTR_TEMPERATURE: self.coordinator.temperature,
ATTR_MAX_TOKENS: self.coordinator.max_tokens,
}
try: try:
history = list(self.coordinator.data.items()) history = list(self.coordinator.data.items())
if not history: if not history:
return None return {
ATTR_TOTAL_RESPONSES: 0,
ATTR_MODEL: self.coordinator.model,
ATTR_TEMPERATURE: self.coordinator.temperature,
ATTR_MAX_TOKENS: self.coordinator.max_tokens,
}
last_question, last_data = history[-1] last_question, last_data = history[-1]
# Handle different response formats
if isinstance(last_data, dict): if isinstance(last_data, dict):
last_response = last_data.get("response", "") last_response = last_data.get("response", "")
last_updated = last_data.get("timestamp", self.coordinator.last_update_success_time)
else: else:
last_response = str(last_data) last_response = str(last_data)
last_updated = self.coordinator.last_update_success_time
# Convert timestamp to local time if needed
if isinstance(last_updated, datetime):
last_updated = dt_util.as_local(last_updated)
return { return {
ATTR_QUESTION: last_question, ATTR_QUESTION: last_question,
ATTR_RESPONSE: last_response, ATTR_RESPONSE: last_response,
ATTR_LAST_UPDATED: self.coordinator.last_update_success_time, ATTR_LAST_UPDATED: last_updated,
ATTR_TOTAL_RESPONSES: len(history),
ATTR_MODEL: self.coordinator.model,
ATTR_TEMPERATURE: self.coordinator.temperature,
ATTR_MAX_TOKENS: self.coordinator.max_tokens,
}
except Exception as err:
_LOGGER.error("Error getting attributes: %s", err, exc_info=True)
return {
ATTR_TOTAL_RESPONSES: 0,
ATTR_MODEL: self.coordinator.model,
ATTR_TEMPERATURE: self.coordinator.temperature,
ATTR_MAX_TOKENS: self.coordinator.max_tokens,
} }
except (IndexError, KeyError, AttributeError) as err:
_LOGGER.warning("Error getting attributes: %s", err)
return None
@property @property
def available(self) -> bool: def available(self) -> bool:
@@ -92,3 +131,8 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
def should_poll(self) -> bool: def should_poll(self) -> bool:
"""No need to poll. Coordinator notifies entity of updates.""" """No need to poll. Coordinator notifies entity of updates."""
return False return False
async def async_added_to_hass(self) -> None:
"""When entity is added to hass."""
await super().async_added_to_hass()
self._handle_coordinator_update()
+52 -15
View File
@@ -1,35 +1,49 @@
ask_question: ask_question:
name: Ask Question name: Ask Question
description: Send a question to the AI model and receive a detailed response description: >-
Send a question to the AI model and receive a detailed response.
The response will be stored in the conversation history and can be retrieved later.
fields: fields:
question: question:
name: Question name: Question
description: Your question or prompt for the AI assistant description: >-
Your question or prompt for the AI assistant. Be specific and clear for better results.
You can ask about home automation, technical advice, or general questions.
required: true required: true
example: "What automations would you recommend for a smart kitchen?" example: |
What automations would you recommend for a smart kitchen?
Consider energy efficiency and convenience.
selector: selector:
text: text:
multiline: true multiline: true
type: text
model: model:
name: Model name: Model
description: Select an AI model to use (optional, overrides default setting) description: >-
Select an AI model to use (optional, overrides default setting).
Different models have different capabilities and token limits.
required: false required: false
example: "gpt-3.5-turbo" example: "gpt-3.5-turbo"
default: "gpt-3.5-turbo" default: "gpt-3.5-turbo"
selector: selector:
select: select:
options: options:
- label: "GPT-3.5 Turbo" - label: "GPT-3.5 Turbo (Fast & Efficient)"
value: "gpt-3.5-turbo" value: "gpt-3.5-turbo"
- label: "GPT-4" - label: "GPT-4 (Most Capable)"
value: "gpt-4" value: "gpt-4"
- label: "GPT-4 32K" - label: "GPT-4 32K (Extended Context)"
value: "gpt-4-32k" value: "gpt-4-32k"
mode: dropdown
temperature: temperature:
name: Temperature name: Temperature
description: "Controls response creativity (0-2): Lower values for focused responses, higher for creative ones" description: >-
Controls response creativity (0-2):
0.0-0.3: Focused, consistent responses
0.4-0.7: Balanced responses
0.8-2.0: More creative, varied responses
required: false required: false
default: 0.7 default: 0.7
selector: selector:
@@ -38,10 +52,16 @@ ask_question:
max: 2.0 max: 2.0
step: 0.1 step: 0.1
mode: slider mode: slider
unit_of_measurement: ""
max_tokens: max_tokens:
name: Max Tokens name: Max Tokens
description: Maximum length of the response description: >-
Maximum length of the response. Higher values allow longer responses but use more API tokens.
Recommended ranges:
- Short responses: 256-512
- Medium responses: 512-1024
- Long responses: 1024-4096
required: false required: false
default: 1000 default: 1000
selector: selector:
@@ -53,16 +73,22 @@ ask_question:
clear_history: clear_history:
name: Clear History name: Clear History
description: Delete all stored questions and responses description: >-
Delete all stored questions and responses from the conversation history.
This action cannot be undone.
fields: {} fields: {}
get_history: get_history:
name: Get History name: Get History
description: Retrieve recent conversation history description: >-
Retrieve recent conversation history, including questions, responses, and timestamps.
Results are ordered from newest to oldest.
fields: fields:
limit: limit:
name: Limit name: Limit
description: Number of most recent conversations to return description: >-
Number of most recent conversations to return.
Higher values return more history but may take longer to process.
required: false required: false
default: 10 default: 10
selector: selector:
@@ -74,13 +100,24 @@ get_history:
set_system_prompt: set_system_prompt:
name: Set System Prompt name: Set System Prompt
description: Configure the AI's behavior by setting a system prompt description: >-
Configure the AI's behavior by setting a system prompt.
This affects how the AI interprets and responds to all future questions.
fields: fields:
prompt: prompt:
name: System Prompt name: System Prompt
description: Instructions that define how the AI should behave and respond description: >-
Instructions that define how the AI should behave and respond.
Be specific about the desired expertise, tone, and format of responses.
required: true required: true
example: "You are a home automation expert assistant. Provide practical advice focused on smart home technology." example: |
You are a home automation expert assistant. Focus on:
1. Practical and efficient solutions
2. Energy-saving recommendations
3. Integration with popular smart home platforms
4. Security and privacy considerations
Provide detailed but concise responses with clear steps when applicable.
selector: selector:
text: text:
multiline: true multiline: true
type: text
@@ -3,14 +3,32 @@
"step": { "step": {
"user": { "user": {
"title": "Set up HA text AI", "title": "Set up HA text AI",
"description": "Configure your OpenAI integration for smart home interactions", "description": "Configure your OpenAI integration for smart home interactions. You'll need an OpenAI API key to proceed.",
"data": { "data": {
"api_key": "OpenAI API Key", "api_key": {
"model": "AI Model", "name": "OpenAI API Key",
"temperature": "Temperature", "description": "Your OpenAI API key from platform.openai.com"
"max_tokens": "Max Tokens", },
"api_endpoint": "API Endpoint", "model": {
"request_interval": "Request Interval" "name": "AI Model",
"description": "Select the AI model to use. GPT-3.5-Turbo is recommended for most uses."
},
"temperature": {
"name": "Temperature",
"description": "Controls response creativity (0-2). Lower values for focused responses, higher for creative ones."
},
"max_tokens": {
"name": "Max Tokens",
"description": "Maximum length of responses. Higher values allow longer responses but use more API tokens."
},
"api_endpoint": {
"name": "API Endpoint",
"description": "OpenAI API endpoint URL. Leave default unless using a custom endpoint."
},
"request_interval": {
"name": "Request Interval",
"description": "Minimum time between API requests in seconds. Increase if hitting rate limits."
}
} }
} }
}, },
@@ -18,22 +36,36 @@
"invalid_auth": "Invalid API key. Please check your OpenAI API key and try again.", "invalid_auth": "Invalid API key. Please check your OpenAI API key and try again.",
"cannot_connect": "Failed to connect to API. Please check your internet connection and API endpoint.", "cannot_connect": "Failed to connect to API. Please check your internet connection and API endpoint.",
"unknown": "Unexpected error occurred. Please check the logs for more details.", "unknown": "Unexpected error occurred. Please check the logs for more details.",
"already_exists": "This API key is already configured in another integration." "already_exists": "This API key is already configured in another integration.",
"invalid_model": "Selected model is not available. Please choose a different model.",
"rate_limit": "API rate limit exceeded. Please try again later or increase the request interval.",
"context_length": "Input too long for selected model. Try reducing max tokens or using a model with larger context.",
"api_error": "OpenAI API error. Please check the logs for details."
}, },
"abort": { "abort": {
"already_configured": "This OpenAI integration is already configured", "already_configured": "This OpenAI integration is already configured",
"auth_failed": "Authentication failed. Please verify your API key." "auth_failed": "Authentication failed. Please verify your API key.",
"invalid_endpoint": "Invalid API endpoint URL provided"
} }
}, },
"options": { "options": {
"step": { "step": {
"init": { "init": {
"title": "HA text AI Options", "title": "HA text AI Options",
"description": "Adjust your OpenAI integration settings", "description": "Adjust your OpenAI integration settings. Changes will apply to future requests.",
"data": { "data": {
"temperature": "Temperature", "temperature": {
"max_tokens": "Max Tokens", "name": "Temperature",
"request_interval": "Request Interval" "description": "Controls response creativity (0-2). Lower values for focused responses, higher for creative ones."
},
"max_tokens": {
"name": "Max Tokens",
"description": "Maximum length of responses. Higher values allow longer responses but use more API tokens."
},
"request_interval": {
"name": "Request Interval",
"description": "Minimum time between API requests in seconds. Increase if hitting rate limits."
}
} }
} }
} }
@@ -44,16 +76,67 @@
"name": "Last Response", "name": "Last Response",
"state_attributes": { "state_attributes": {
"last_updated": { "last_updated": {
"name": "Last Updated" "name": "Last Updated",
"description": "Time of the last AI response"
}, },
"question": { "question": {
"name": "Last Question" "name": "Last Question",
"description": "Most recent question asked"
}, },
"response": { "response": {
"name": "AI Response" "name": "AI Response",
"description": "Latest response from the AI"
},
"model": {
"name": "Current Model",
"description": "AI model currently in use"
},
"temperature": {
"name": "Temperature Setting",
"description": "Current temperature parameter"
},
"max_tokens": {
"name": "Max Tokens Setting",
"description": "Current maximum tokens limit"
},
"total_responses": {
"name": "Total Responses",
"description": "Number of responses since last reset"
},
"system_prompt": {
"name": "System Prompt",
"description": "Current system instructions for the AI"
},
"response_time": {
"name": "Response Time",
"description": "Time taken to generate last response"
} }
} }
} }
} }
},
"services": {
"ask_question": {
"name": "Ask Question",
"description": "Send a question to the AI model",
"fields": {
"question": {
"name": "Question",
"description": "Your question for the AI"
}
}
},
"clear_history": {
"name": "Clear History",
"description": "Clear conversation history"
},
"get_history": {
"name": "Get History",
"description": "Retrieve conversation history"
},
"set_system_prompt": {
"name": "Set System Prompt",
"description": "Set AI behavior instructions"
}
} }
} }
BIN
View File
Binary file not shown.
BIN
View File
Binary file not shown.
+1 -1
View File
@@ -4,6 +4,6 @@
"domains": ["sensor"], "domains": ["sensor"],
"homeassistant": "2024.11.0", "homeassistant": "2024.11.0",
"icon": "mdi:brain", "icon": "mdi:brain",
"version": "1.0.2", "version": "1.0.3",
"documentation": "https://github.com/smkrv/ha-text-ai" "documentation": "https://github.com/smkrv/ha-text-ai"
} }