diff --git a/README.md b/README.md
index 3a7c2b1..c6b2d99 100644
--- a/README.md
+++ b/README.md
@@ -239,6 +239,12 @@ data:
### 🏷️ HA Text AI Sensor Naming Convention
+#### Character Restrictions
+- Only lowercase letters (a-z)
+- Numbers (0-9)
+- Underscore (_)
+- Maximum length: 50 characters (including `ha_text_ai_` prefix (14 characters)
+
#### Sensor Name Structure
```yaml
# Always starts with 'sensor.ha_text_ai_'
diff --git a/README_RU.md b/README_RU.md
deleted file mode 100644
index 9f82d7a..0000000
--- a/README_RU.md
+++ /dev/null
@@ -1,390 +0,0 @@
-# 🤖 HA Text AI для Home Assistant
-
-
-
-  [](https://opensource.org/licenses/MIT) [](https://github.com/hacs/integration)   
-
-

-
-### Расширенная AI-интеграция для Home Assistant с поддержкой нескольких провайдеров
-
-
-
-Преобразите свой умный дом с мощной AI-поддержкой от множества провайдеров, включая модели OpenAI GPT и Anthropic Claude. Получайте интеллектуальные ответы, автоматизируйте сложные сценарии и улучшайте домашнюю автоматизацию с помощью передовой обработки естественного языка.
-
-
-
----
-
-> [!IMPORTANT]
-> 🚧 АЛЬФА-ВЕРСИЯ 🚧
-> Ожидайте: возможные ошибки, частые изменения, незавершенные функции.
-> 🤝 Разработка силами сообщества
->
->
-
-## 🌟 Возможности
-
-- 🧠 **Мультипровайдерная AI-интеграция**:
- - Поддержка моделей OpenAI GPT
- - Интеграция с Anthropic Claude
- - Пользовательские API-эндпоинты
- - Гибкий выбор модели
-
-- 💬 **Расширенная обработка языка**:
- - Контекстно-зависимые ответы
- - Многоступенчатые диалоги
- - Пользовательские системные инструкции
- - Естественный поток общения
-
-- 📝 **Улучшенное управление памятью**:
- - Постоянная история диалогов
- - Контекстно-осознанные ответы
- - Настраиваемые лимиты истории
- - Фильтрация для конкретных моделей
-
-- ⚡ **Оптимизация производительности**:
- - Эффективное использование токенов
- - Интеллектуальное ограничение скорости
- - Кэширование ответов
- - Контроль интервалов запросов
-
-- 🎯 **Расширенная настройка**:
- - Выбор модели для каждого запроса
- - Настраиваемые параметры
- - Пользовательские системные промпты
- - Контроль температуры
-
-- 🔒 **Повышенная безопасность**:
- - Защищенное хранение API-ключей
- - Защита от превышения лимитов
- - Обработка ошибок
- - Мониторинг использования
-
-- 🎨 **Улучшенный пользовательский интерфейс**:
- - Интуитивная конфигурация
- - Подробные атрибуты сенсоров
- - Расширенный сервисный интерфейс
- - UI выбора модели
-
-- 🔄 **Интеграция с автоматизацией**:
- - Реакции на события
- - Поддержка условной логики
- - Совместимость с шаблонами
- - Автоматизация для конкретных моделей
-
-## 📋 Предварительные требования
-
-- Home Assistant 2024.11 или новее
-- Активный API-ключ от:
- - OpenAI ([Получить ключ](https://platform.openai.com/account/api-keys))
- - Anthropic ([Получить ключ](https://console.anthropic.com/))
- - OpenRouter ([Получить ключ](https://openrouter.ai/keys))
-- Python 3.9 или новее
-- Стабильное интернет-соединение
-
-### Параметры конфигурации
-- Провайдер API (OpenAI/Anthropic)
-- API-ключ (специфичный для провайдера)
-- Выбор модели (гибкий, модели от провайдера)
-- Температура (управление креативностью, 0.0-2.0)
-- Максимальные токены (Ограничение длины ответа)
-- Интервал запросов (Регулировка вызовов API)
-- Пользовательская конечная точка API (опционально)
-
-#### ⓘ Потенциально совместимые провайдеры
-Интеграция разработана гибко и может работать с провайдерами, предлагающими API, совместимые с OpenAI:
-- Groq
-- Together AI
-- Perplexity AI
-- Mistral AI
-- Google AI
-- Локальные AI-серверы (например, Ollama)
-- Пользовательские эндпоинты, совместимые с OpenAI
-
-#### Дополнительные замечания
-- Не все провайдеры гарантируют полную совместимость
-- Производительность может различаться
-- Проверяйте документацию каждого провайдера
-- Убедитесь, что у вашего API-ключа достаточно кредитов/квоты
-
-#### Требования к совместимости провайдеров
-Для совместимости провайдер должен поддерживать:
-- REST API-структуру, похожую на OpenAI
-- JSON-формат запросов/ответов
-- Стандартный метод аутентификации
-- Схожую обработку параметров модели
-
-## ⚡ Установка
-
-### Установка через HACS (Рекомендуется)
-
-1\. Откройте HACS в Home Assistant
-2\. Нажмите "Интеграции"
-3\. Нажмите "..." в правом верхнем углу
-4\. Выберите "Пользовательские репозитории"
-5\. Добавьте URL репозитория: `https://github.com/smkrv/ha-text-ai`
-6\. Выберите категорию "Интеграция"
-7\. Нажмите "Загрузить"
-8\. Перезапустите Home Assistant
-
-### Самостоятельная установка
-1\. Скачайте последний релиз
-2\. Извлеките и скопируйте `custom_components/ha_text_ai` в вашу директорию `custom_components`
-3\. Перезапустите Home Assistant
-4\. Добавьте конфигурацию через UI или YAML
-
-## ⚙️ Конфигурация
-
-### Через UI (Рекомендуется)
-1\. Перейдите в Настройки → Устройства и сервисы
-2\. Нажмите "Добавить интеграцию"
-3\. Найдите "HA Text AI"
-4\. Следуйте шагам конфигурации
-
-### Через YAML
-```yaml
-ha_text_ai:
- api_provider: openai # или anthropic
- api_key: !secret ai_api_key
- model: gpt-4o-mini
- temperature: 0.7
- max_tokens: 1000
- request_interval: 1.0
- api_endpoint: https://api.openai.com/v1 # опционально, для пользовательских эндпоинтов
- system_prompt: |
- Вы - эксперт по домашней автоматизации.
- Фокусируйтесь на практичных и эффективных решениях.
-```
-
-## 🛠️ Доступные сервисы
-
-### ask_question
-```yaml
-service: ha_text_ai.ask_question
-data:
- question: "Какая оптимальная температура для сна?"
- model: "claude-3-sonnet" # опционально
- temperature: 0.5 # опционально
- max_tokens: 500 # опционально
- context_messages: 10 # опционально, количество предыдущих сообщений в контексте, по умолчанию: 5
- system_prompt: "Вы эксперт по оптимизации сна" # опционально
-```
-
-### set_system_prompt
-```yaml
-service: ha_text_ai.set_system_prompt
-data:
- prompt: |
- Вы эксперт по домашней автоматизации, сосредоточенный на:
- 1. Энергоэффективности
- 2. Оптимизации комфорта
- 3. Вопросах безопасности
- Предоставляйте практичные, выполнимые советы.
-```
-
-### clear_history
-```yaml
-service: ha_text_ai.clear_history
-```
-
-### get_history
-```yaml
-service: ha_text_ai.get_history
-data:
- limit: 5 # опционально
- filter_model: "gpt-4o" # опционально
-```
-
-### 🏷️ Соглашение по именованию сенсоров HA Text AI
-
-#### Структура имени сенсора
-```yaml
-# Всегда начинается с 'sensor.ha_text_ai_'
-# Вы определяете только часть после подчеркивания
-sensor.ha_text_ai_ВАШЕ_УНИКАЛЬНОЕ_ОКОНЧАНИЕ
-
-# Примеры:
-sensor.ha_text_ai_gpt # Сенсор на базе GPT
-sensor.ha_text_ai_claude # Сенсор на базе Claude
-sensor.ha_text_ai_gpt # Пользовательское окончание
-```
-
-#### Получение ответа
-```yaml
-# Используйте имя вашего конкретного сенсора
-{{ state_attr('sensor.ha_text_ai_gpt', 'response') }}
-```
-
-#### Практическое использование
-```yaml
-automation:
- - alias: "AI-ответ с пользовательским сенсором"
- action:
- - service: ha_text_ai.ask_question
- data:
- question: "Совет по домашней автоматизации"
- - service: notify.mobile
- data:
- message: >
- Совет ИИ:
- {{ state_attr('sensor.ha_text_ai_gpt', 'response') }}
-```
-
-### 💡 Правила именования
-- Префикс всегда `sensor.ha_text_ai_`
-- Добавьте уникальный идентификатор после подчеркивания
-- Используйте lowercase
-- Пробелы не допускаются
-- Сохраняйте описательность и краткость
-
-### 🔍 Атрибуты сенсора HA Text AI
-
-#### Информация о модели и провайдере
-```yaml
-# Название текущей используемой AI-модели
-{{ state_attr('sensor.ha_text_ai_gpt', 'Model') }} # gpt-4o
-
-# Провайдер сервиса (определяет эндпоинт API и аутентификацию)
-{{ state_attr('sensor.ha_text_ai_gpt', 'Api provider') }} # openai
-
-# Предыдущая или альтернативная конфигурация модели
-{{ state_attr('sensor.ha_text_ai_gpt', 'Last model') }} # gpt-4o
-```
-
-#### Системный статус
-```yaml
-# Текущая готовность сервиса AI API
-{{ state_attr('sensor.ha_text_ai_gpt', 'Api status') }} # готов
-
-# Указывает, выполняется ли в данный момент запрос
-{{ state_attr('sensor.ha_text_ai_gpt', 'Is processing') }} # false
-
-# Показывает, достигнут ли лимит запросов API
-{{ state_attr('sensor.ha_text_ai_gpt', 'Is rate limited') }} # false
-
-# Статус конкретного используемого эндпоинта
-{{ state_attr('sensor.ha_text_ai_gpt', 'Endpoint status') }} # готов
-```
-
-#### Метрики производительности
-```yaml
-# Общее количество успешно выполненных API-запросов
-{{ state_attr('sensor.ha_text_ai_gpt', 'Successful requests') }} # 0
-
-# Количество API-запросов с ошибками
-{{ state_attr('sensor.ha_text_ai_gpt', 'Failed requests') }} # 0
-
-# Среднее время получения ответа от сервиса AI
-{{ state_attr('sensor.ha_text_ai_gpt', 'Average latency') }} # 0
-
-# Максимальное время для одного цикла запрос-ответ
-{{ state_attr('sensor.ha_text_ai_gpt', 'Max latency') }} # 0
-```
-
-#### Использование диалога и токенов
-```yaml
-# Количество предыдущих взаимодействий, сохраненных в контексте
-{{ state_attr('sensor.ha_text_ai_gpt', 'History size') }} # 0
-
-# Общее количество использованных токенов
-{{ state_attr('sensor.ha_text_ai_gpt', 'Total tokens') }} # 0
-
-# Токены, использованные во входных промптах
-{{ state_attr('sensor.ha_text_ai_gpt', 'Prompt tokens') }} # 0
-
-# Токены, использованные в ответах AI
-{{ state_attr('sensor.ha_text_ai_gpt', 'Completion tokens') }} # 0
-```
-
-#### Детали последнего взаимодействия
-```yaml
-# Самый последний полный ответ, сгенерированный сервисом AI
-{{ state_attr('sensor.ha_text_ai_gpt', 'Response') }} # Последний AI-ответ
-
-# Последний обработанный пользовательский запрос
-{{ state_attr('sensor.ha_text_ai_gpt', 'Question') }} # Последний заданный вопрос
-
-# Точный момент последнего взаимодействия
-{{ state_attr('sensor.ha_text_ai_gpt', 'Last timestamp') }} # Временная метка
-```
-
-#### Системное здоровье
-```yaml
-# Суммарное количество всех ошибок при взаимодействии с сервисом AI
-{{ state_attr('sensor.ha_text_ai_gpt', 'Total errors') }} # 0
-
-# Указывает, проводится ли плановое или аварийное обслуживание
-{{ state_attr('sensor.ha_text_ai_gpt', 'Is maintenance') }} # false
-
-# Общее время непрерывной работы сервиса AI (в часах или днях)
-{{ state_attr('sensor.ha_text_ai_gpt', 'Uptime') }} # 547,58
-```
-
-### 💡 Профессиональные советы
-- Всегда проверяйте наличие атрибутов
-- Используйте эти атрибуты для мониторинга и автоматизации
-- Некоторые значения могут быть изначально равны 0 или быть пустыми
-
-## 📘 Часто задаваемые вопросы
-
-**В: Какие AI-провайдеры поддерживаются?**
-О: В настоящее время поддерживаются OpenAI (модели GPT) и Anthropic (модели Claude), планируется расширение списка провайдеров.
-
-**В: Как уменьшить расходы на API?**
-О: Используйте GPT-3.5-Turbo или Claude-3-Sonnet для большинства запросов, внедрите кэширование и оптимизируйте использование токенов.
-
-**В: Есть ли ограничения на количество запросов?**
-О: Зависит от тарифного плана вашего API-провайдера. Рекомендуется отслеживать использование и реализовать ограничение запросов через параметр `request_interval`.
-
-**В: Можно ли использовать пользовательские модели?**
-О: Да, вы можете настроить пользовательские эндпоинты и использовать любую совместимую модель, указав её в конфигурации.
-
-**В: Как переключаться между различными AI-провайдерами?**
-О: Просто измените параметр модели в вашей конфигурации или при вызове сервиса для использования нужной модели провайдера.
-
-**В: Насколько безопасны мои данные?**
-О: Ваши данные в безопасности. Система работает полностью на вашем локальном компьютере, сохраняя контроль над данными. API-ключи хранятся защищённо, все внешние коммуникации используют зашифрованные соединения.
-
-**В: Как работают контекстные сообщения?**
-О: Контекстные сообщения позволяют AI помнить и ссылаться на предыдущую историю диалога. По умолчанию включаются 5 предыдущих сообщений, но вы можете настроить от 1 до 20 сообщений для контроля глубины разговора и использования токенов.
-
-## 🤝 Участие в разработке
-
-Мы всегда рады вашему вкладу! Пожалуйста, ознакомьтесь с [Руководством по участию](CONTRIBUTING.md).
-
-1\. Создайте fork репозитория
-2\. Создайте ветку для функции (`git checkout -b feature/Улучшение`)
-3\. Зафиксируйте изменения (`git commit -m 'Добавить улучшение'`)
-4\. Отправьте ветку (`git push origin feature/Улучшение`)
-5\. Откройте Pull Request
-
-## 📝 Лицензия
-
-MIT License - подробности в файле [LICENSE](LICENSE).
-
-## 💡 Поддержите проект
-
-Лучшая поддержка - это:
-- Делитесь обратной связью
-- Предлагайте идеи
-- Рекомендуйте друзьям
-- Сообщайте о проблемах
-- Поставьте звезду репозиторию
-
-Если хотите финансово поблагодарить, можете отправить небольшой токен признательности в USDT:
-
-**USDT Кошелек (TRC10/TRC20):**
-`TXC9zYHYPfWUGi4Sv4R1ctTBGScXXQk5HZ`
-
-*Open-source создается страстью сообщества!* 🚀
-
----
-
-
-
-Создано с ❤️ и Claude 3.5 Sonnet для сообщества Home Assistant
-
-[Сообщить об ошибке](https://github.com/smkrv/ha-text-ai/issues) · [Предложить функцию](https://github.com/smkrv/ha-text-ai/issues)
-
-
diff --git a/custom_components/ha_text_ai/__init__.py b/custom_components/ha_text_ai/__init__.py
index 5c639e0..f7666e2 100644
--- a/custom_components/ha_text_ai/__init__.py
+++ b/custom_components/ha_text_ai/__init__.py
@@ -43,6 +43,8 @@ from .const import (
SERVICE_CLEAR_HISTORY,
SERVICE_GET_HISTORY,
SERVICE_SET_SYSTEM_PROMPT,
+ DEFAULT_MAX_HISTORY,
+ CONF_MAX_HISTORY_SIZE,
)
_LOGGER = logging.getLogger(__name__)
@@ -197,11 +199,14 @@ async def async_check_api(session, endpoint: str, headers: dict, provider: str)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up HA Text AI from a config entry."""
+ _LOGGER.debug(f"Setting up HA Text AI entry: {entry.data}")
+
try:
if CONF_API_PROVIDER not in entry.data:
_LOGGER.error("API provider not specified")
raise ConfigEntryNotReady("API provider is required")
+ # Get configuration
session = aiohttp_client.async_get_clientsession(hass)
api_provider = entry.data.get(CONF_API_PROVIDER)
model = entry.data.get(CONF_MODEL, DEFAULT_MODEL)
@@ -212,6 +217,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
).rstrip('/')
api_key = entry.data[CONF_API_KEY]
instance_name = entry.data.get(CONF_NAME, entry.entry_id)
+ request_interval = entry.data.get(CONF_REQUEST_INTERVAL, DEFAULT_REQUEST_INTERVAL)
+ max_tokens = entry.data.get(CONF_MAX_TOKENS, DEFAULT_MAX_TOKENS)
+ temperature = entry.data.get(CONF_TEMPERATURE, DEFAULT_TEMPERATURE)
+ max_history_size = entry.data.get(CONF_MAX_HISTORY_SIZE, DEFAULT_MAX_HISTORY)
+ context_messages = entry.data.get(CONF_CONTEXT_MESSAGES, DEFAULT_CONTEXT_MESSAGES)
is_anthropic = api_provider == API_PROVIDER_ANTHROPIC
headers = {
@@ -242,39 +252,33 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
hass=hass,
client=api_client,
model=model,
- update_interval=entry.data.get(CONF_REQUEST_INTERVAL, DEFAULT_REQUEST_INTERVAL),
+ update_interval=request_interval,
instance_name=instance_name,
- max_tokens=entry.data.get(CONF_MAX_TOKENS, DEFAULT_MAX_TOKENS),
- temperature=entry.data.get(CONF_TEMPERATURE, DEFAULT_TEMPERATURE),
+ max_tokens=max_tokens,
+ temperature=temperature,
+ max_history_size=max_history_size,
+ context_messages=context_messages,
is_anthropic=is_anthropic,
- context_messages=entry.data.get(
- CONF_CONTEXT_MESSAGES,
- DEFAULT_CONTEXT_MESSAGES
- ),
)
- coordinator.data = coordinator._initial_state.copy()
- _LOGGER.debug(f"Initial state set for coordinator {instance_name}")
-
- await coordinator.async_config_entry_first_refresh()
+ _LOGGER.debug(f"Created coordinator for {instance_name}")
+ # Store coordinator
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = coordinator
+ _LOGGER.debug(f"Stored coordinator in hass.data[{DOMAIN}][{entry.entry_id}]")
+
+ # Set up platforms
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
- _LOGGER.info(
- "Successfully set up %s instance '%s' with model %s",
- api_provider,
- instance_name,
- model
- )
+ _LOGGER.debug(f"Setup completed for {instance_name}")
return True
- except Exception as ex:
- _LOGGER.exception("Setup error: %s", str(ex))
- raise ConfigEntryNotReady(f"Setup error: {str(ex)}") from ex
+ except Exception as err:
+ _LOGGER.exception(f"Error setting up HA Text AI: {err}")
+ raise
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
diff --git a/custom_components/ha_text_ai/api_client.py b/custom_components/ha_text_ai/api_client.py
index 727af26..86cb553 100644
--- a/custom_components/ha_text_ai/api_client.py
+++ b/custom_components/ha_text_ai/api_client.py
@@ -19,6 +19,7 @@ from .const import (
_LOGGER = logging.getLogger(__name__)
+
class APIClient:
"""API Client for OpenAI and Anthropic."""
@@ -59,6 +60,7 @@ class APIClient:
payload: Dict[str, Any],
) -> Dict[str, Any]:
"""Make API request with retry logic."""
+ _LOGGER.debug(f"API Request: URL={url}, Payload={payload}")
for attempt in range(API_RETRY_COUNT):
try:
async with timeout(API_TIMEOUT):
@@ -66,20 +68,23 @@ class APIClient:
url,
json=payload,
headers=self.headers,
- timeout=self.timeout
+ timeout=self.timeout,
) as response:
+ _LOGGER.debug(f"Response status: {response.status}")
if response.status != 200:
error_data = await response.json()
+ _LOGGER.error(f"API error: {error_data}")
raise HomeAssistantError(f"API error: {error_data}")
return await response.json()
except asyncio.TimeoutError:
+ _LOGGER.warning(f"Timeout on attempt {attempt + 1}")
if attempt == API_RETRY_COUNT - 1:
raise HomeAssistantError("API request timed out")
await asyncio.sleep(1 * (attempt + 1))
except Exception as e:
+ _LOGGER.warning(f"API request failed on attempt {attempt + 1}: {str(e)}")
if attempt == API_RETRY_COUNT - 1:
raise
- _LOGGER.warning("API request failed, retrying: %s", str(e))
await asyncio.sleep(1 * (attempt + 1))
async def create(
@@ -125,16 +130,14 @@ class APIClient:
return {
"choices": [
{
- "message": {
- "content": data["choices"][0]["message"]["content"]
- }
+ "message": {"content": data["choices"][0]["message"]["content"]},
}
],
"usage": {
"prompt_tokens": data["usage"]["prompt_tokens"],
"completion_tokens": data["usage"]["completion_tokens"],
- "total_tokens": data["usage"]["total_tokens"]
- }
+ "total_tokens": data["usage"]["total_tokens"],
+ },
}
async def _create_anthropic_completion(
@@ -147,19 +150,24 @@ class APIClient:
"""Create completion using Anthropic API."""
url = f"{self.endpoint}/v1/messages"
- # Convert messages to Anthropic format
- system_prompt = next(
- (msg["content"] for msg in messages if msg["role"] == "system"),
- None
- )
- conversation = [msg for msg in messages if msg["role"] != "system"]
+ system_prompt = None
+ filtered_messages = []
+ for msg in messages:
+ if msg['role'] == 'system':
+ if system_prompt is None:
+ system_prompt = msg['content']
+ else:
+ system_prompt += f" {msg['content']}"
+ else:
+ filtered_messages.append(msg)
payload = {
"model": model,
- "messages": conversation,
+ "messages": filtered_messages,
"max_tokens": max_tokens,
"temperature": temperature,
}
+
if system_prompt:
payload["system"] = system_prompt
@@ -167,14 +175,26 @@ class APIClient:
return {
"choices": [
{
- "message": {
- "content": data["content"][0]["text"]
- }
+ "message": {"content": data["content"][0]["text"]},
}
],
"usage": {
"prompt_tokens": data["usage"]["input_tokens"],
"completion_tokens": data["usage"]["output_tokens"],
- "total_tokens": data["usage"]["input_tokens"] + data["usage"]["output_tokens"]
- }
+ "total_tokens": data["usage"]["input_tokens"] + data["usage"]["output_tokens"],
+ },
}
+
+ async def check_connection(self) -> bool:
+ """Check API connection."""
+ try:
+ await self._make_request(self.endpoint, {"test": "connection"})
+ return True
+ except Exception as e:
+ _LOGGER.error(f"Connection check failed: {str(e)}")
+ return False
+
+ async def shutdown(self) -> None:
+ """Shutdown API client."""
+ _LOGGER.debug("Shutting down API client")
+ await self.session.close()
diff --git a/custom_components/ha_text_ai/config_flow.py b/custom_components/ha_text_ai/config_flow.py
index b87f3d4..c666299 100644
--- a/custom_components/ha_text_ai/config_flow.py
+++ b/custom_components/ha_text_ai/config_flow.py
@@ -34,10 +34,21 @@ from .const import (
MIN_MAX_TOKENS,
MAX_MAX_TOKENS,
MIN_REQUEST_INTERVAL,
+ DEFAULT_NAME_PREFIX,
+ DEFAULT_MAX_HISTORY,
+ CONF_MAX_HISTORY_SIZE,
)
_LOGGER = logging.getLogger(__name__)
+
+def normalize_name(name: str) -> str:
+ """Normalize name to conform to HA naming convention using underscores."""
+ normalized = ''.join(c if c.isalnum() or c == '_' else '_' for c in name)
+ normalized = '_'.join(filter(None, normalized.split('_')))
+ return normalized.lower()
+
+
class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Handle a config flow for HA text AI."""
@@ -69,18 +80,18 @@ class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_provider(self, user_input: Optional[Dict[str, Any]] = None) -> FlowResult:
"""Handle provider configuration step."""
+ self._errors = {}
+
if user_input is None:
default_endpoint = (
DEFAULT_OPENAI_ENDPOINT if self._provider == API_PROVIDER_OPENAI
else DEFAULT_ANTHROPIC_ENDPOINT
)
- suggested_name = f"HA Text AI {len(self._async_current_entries()) + 1}"
-
return self.async_show_form(
step_id="provider",
data_schema=vol.Schema({
- vol.Required(CONF_NAME, default=suggested_name): str,
+ vol.Required(CONF_NAME, default="my_assistant"): str,
vol.Required(CONF_API_KEY): str,
vol.Required(CONF_MODEL, default=DEFAULT_MODEL): str,
vol.Required(CONF_API_ENDPOINT, default=default_endpoint): str,
@@ -103,27 +114,86 @@ class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
vol.Coerce(int),
vol.Range(min=1, max=20)
),
- }),
- errors=self._errors
+ vol.Optional(
+ CONF_MAX_HISTORY_SIZE,
+ default=DEFAULT_MAX_HISTORY
+ ): vol.All(
+ vol.Coerce(int),
+ vol.Range(min=1, max=100)
+ ),
+ })
)
- instance_name = user_input[CONF_NAME]
- await self._async_validate_name(instance_name)
- if self._errors:
- return await self.async_step_provider()
+ input_copy = user_input.copy()
- if not await self._async_validate_api(user_input):
- return await self.async_step_provider()
+ try:
+ normalized_name = self._validate_and_normalize_name(input_copy[CONF_NAME])
+ input_copy[CONF_NAME] = normalized_name
+ except ValueError as e:
+ return self.async_show_form(
+ step_id="provider",
+ data_schema=vol.Schema({
+ vol.Required(CONF_NAME, default=input_copy[CONF_NAME]): str,
+ vol.Required(CONF_API_KEY, default=input_copy[CONF_API_KEY]): str,
+ vol.Required(CONF_MODEL, default=input_copy[CONF_MODEL]): str,
+ vol.Required(CONF_API_ENDPOINT, default=input_copy[CONF_API_ENDPOINT]): str,
+ vol.Optional(CONF_TEMPERATURE, default=input_copy.get(CONF_TEMPERATURE, DEFAULT_TEMPERATURE)): vol.All(
+ vol.Coerce(float),
+ vol.Range(min=MIN_TEMPERATURE, max=MAX_TEMPERATURE)
+ ),
+ }),
+ errors={"name": str(e)}
+ )
- return await self._create_entry(user_input)
+ try:
+ if not await self._async_validate_api(input_copy):
+ return self.async_show_form(
+ step_id="provider",
+ data_schema=vol.Schema({
+ }),
+ errors=self._errors
+ )
+ except Exception as e:
+ return self.async_show_form(
+ step_id="provider",
+ data_schema=vol.Schema({
+ }),
+ errors={"base": str(e)}
+ )
+
+ return await self._create_entry(input_copy)
+
+ def _validate_and_normalize_name(self, name: str) -> str:
+ """
+ Validate and normalize name with detailed error handling.
+
+ Raises:
+ ValueError: If name is invalid
+
+ Returns:
+ Normalized name
+ """
+ if not name:
+ raise ValueError("empty")
+
+ name = name.strip()
+ normalized = ''.join(
+ c if c.isalnum() or c in ' _' else '_' # Only allow underscores
+ for c in name
+ )
+
+ normalized = normalized.replace(' ', '_').lower()
- async def _async_validate_name(self, name: str) -> bool:
- """Validate that the name is unique."""
for entry in self._async_current_entries():
- if entry.data.get(CONF_NAME) == name:
- self._errors["name"] = "name_exists"
- return False
- return True
+ if entry.data.get(CONF_NAME, "") == normalized:
+ raise ValueError("name_exists")
+
+ normalized = normalized[:50]
+
+ if not normalized:
+ raise ValueError("empty")
+
+ return normalized
async def _async_validate_api(self, user_input: Dict[str, Any]) -> bool:
"""Validate API connection."""
@@ -167,21 +237,36 @@ class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
}
async def _create_entry(self, user_input: Dict[str, Any]) -> FlowResult:
- """Create the config entry."""
+ """Create the config entry with comprehensive data preservation."""
instance_name = user_input[CONF_NAME]
- unique_id = f"{DOMAIN}_{instance_name}_{self._provider}".lower().replace(" ", "_")
+ normalized_name = normalize_name(instance_name)
+
+ unique_id = f"{DOMAIN}_{normalized_name}_{self._provider}".lower()
+
+ entry_data = {
+ CONF_API_PROVIDER: self._provider,
+ CONF_NAME: instance_name,
+ "normalized_name": normalized_name,
+ CONF_API_KEY: user_input.get(CONF_API_KEY),
+ CONF_API_ENDPOINT: user_input.get(CONF_API_ENDPOINT),
+ "unique_id": unique_id,
+ CONF_MODEL: user_input.get(CONF_MODEL, DEFAULT_MODEL),
+ CONF_TEMPERATURE: user_input.get(CONF_TEMPERATURE, DEFAULT_TEMPERATURE),
+ CONF_MAX_TOKENS: user_input.get(CONF_MAX_TOKENS, DEFAULT_MAX_TOKENS),
+ CONF_REQUEST_INTERVAL: user_input.get(CONF_REQUEST_INTERVAL, DEFAULT_REQUEST_INTERVAL),
+ CONF_CONTEXT_MESSAGES: user_input.get(CONF_CONTEXT_MESSAGES, DEFAULT_CONTEXT_MESSAGES),
+ CONF_MAX_HISTORY_SIZE: user_input.get(CONF_MAX_HISTORY_SIZE, DEFAULT_MAX_HISTORY),
+ }
+
+ for key, value in user_input.items():
+ if key not in entry_data:
+ entry_data[key] = value
+
+ _LOGGER.debug(f"Creating config entry with data: {entry_data}")
return self.async_create_entry(
title=instance_name,
- data={
- CONF_API_PROVIDER: self._provider,
- CONF_NAME: instance_name,
- **user_input,
- "unique_id": unique_id,
- CONF_CONTEXT_MESSAGES: user_input.get(
- CONF_CONTEXT_MESSAGES,
- DEFAULT_CONTEXT_MESSAGES)
- }
+ data=entry_data
)
@staticmethod
@@ -243,5 +328,15 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
vol.Coerce(int),
vol.Range(min=1, max=20)
),
+ vol.Optional(
+ CONF_MAX_HISTORY_SIZE,
+ default=current_data.get(
+ CONF_MAX_HISTORY_SIZE,
+ DEFAULT_MAX_HISTORY
+ )
+ ): vol.All(
+ vol.Coerce(int),
+ vol.Range(min=1, max=100)
+ ),
})
)
diff --git a/custom_components/ha_text_ai/const.py b/custom_components/ha_text_ai/const.py
index ccd41f2..2fa1be2 100644
--- a/custom_components/ha_text_ai/const.py
+++ b/custom_components/ha_text_ai/const.py
@@ -6,7 +6,7 @@ from homeassistant.helpers import config_validation as cv
# Domain and platforms
DOMAIN: Final = "ha_text_ai"
-PLATFORMS: Final = [Platform.SENSOR]
+PLATFORMS: list[str] = ["sensor"]
# Provider configuration
CONF_API_PROVIDER: Final = "api_provider"
@@ -29,7 +29,7 @@ CONF_MAX_TOKENS: Final = "max_tokens"
CONF_API_ENDPOINT: Final = "api_endpoint"
CONF_REQUEST_INTERVAL: Final = "request_interval"
CONF_INSTANCE: Final = "instance"
-CONF_MAX_HISTORY_SIZE: Final = "max_history_size"
+CONF_MAX_HISTORY_SIZE: Final = "max_history_size" # Correct constant name
CONF_IS_ANTHROPIC: Final = "is_anthropic"
CONF_CONTEXT_MESSAGES: Final = "context_messages"
@@ -41,6 +41,7 @@ DEFAULT_REQUEST_INTERVAL: Final = 1.0
DEFAULT_TIMEOUT: Final = 30
DEFAULT_MAX_HISTORY: Final = 50
DEFAULT_NAME: Final = "HA Text AI"
+DEFAULT_NAME_PREFIX = "ha_text_ai"
DEFAULT_CONTEXT_MESSAGES: Final = 5
# Parameter constraints
@@ -164,7 +165,7 @@ SERVICE_SCHEMA_GET_HISTORY = vol.Schema({
vol.Required(CONF_INSTANCE): cv.string,
vol.Optional("limit", default=10): vol.All(
vol.Coerce(int),
- vol.Range(min=1, max=100)
+ vol.Range(min=1, max=100),
),
vol.Optional("filter_model"): cv.string
})
@@ -189,7 +190,7 @@ CONFIG_SCHEMA = vol.Schema({
vol.Coerce(float),
vol.Range(min=MIN_REQUEST_INTERVAL, max=MAX_REQUEST_INTERVAL)
),
- vol.Optional(CONF_MAX_HISTORY_SIZE, default=DEFAULT_MAX_HISTORY): vol.All(
+ vol.Optional(CONF_MAX_HISTORY_SIZE, default=DEFAULT_MAX_HISTORY): vol.All( # Correct usage
vol.Coerce(int),
vol.Range(min=1, max=100),
),
diff --git a/custom_components/ha_text_ai/coordinator.py b/custom_components/ha_text_ai/coordinator.py
index 9344e5f..a2771c9 100644
--- a/custom_components/ha_text_ai/coordinator.py
+++ b/custom_components/ha_text_ai/coordinator.py
@@ -2,6 +2,7 @@
from __future__ import annotations
import logging
+import traceback
from datetime import datetime, timedelta
from typing import Any, Dict, List, Optional
@@ -9,6 +10,8 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from homeassistant.util import dt as dt_util
from homeassistant.exceptions import HomeAssistantError
+from homeassistant.const import CONF_NAME
+from .config_flow import normalize_name
from .const import (
DOMAIN,
@@ -21,11 +24,16 @@ from .const import (
DEFAULT_TEMPERATURE,
DEFAULT_MAX_HISTORY,
DEFAULT_CONTEXT_MESSAGES,
+ DEFAULT_NAME_PREFIX,
+ CONF_MAX_HISTORY_SIZE,
)
_LOGGER = logging.getLogger(__name__)
+
class HATextAICoordinator(DataUpdateCoordinator):
+ """The HA Text AI coordinator."""
+
def __init__(
self,
hass: HomeAssistant,
@@ -41,6 +49,12 @@ class HATextAICoordinator(DataUpdateCoordinator):
) -> None:
"""Initialize coordinator."""
self.instance_name = instance_name
+ self.normalized_name = None
+
+ # Use the normalize_name function from config_flow to ensure consistency
+ from .config_flow import normalize_name
+ self.normalized_name = normalize_name(instance_name)
+
self.hass = hass
self.client = client
self.model = model
@@ -61,7 +75,7 @@ class HATextAICoordinator(DataUpdateCoordinator):
"total_errors": 0,
"average_latency": 0,
"max_latency": 0,
- "min_latency": float('inf'),
+ "min_latency": float("inf"),
},
"last_response": {
"timestamp": dt_util.utcnow().isoformat(),
@@ -69,7 +83,8 @@ class HATextAICoordinator(DataUpdateCoordinator):
"response": "",
"model": model,
"instance": instance_name,
- "error": None
+ "normalized_name": self.normalized_name,
+ "error": None,
},
"is_processing": False,
"is_rate_limited": False,
@@ -105,13 +120,17 @@ class HATextAICoordinator(DataUpdateCoordinator):
self.last_response = self._initial_state["last_response"].copy()
self._start_time = dt_util.utcnow()
- _LOGGER.info(f"Initialized HA Text AI coordinator with instance: {instance_name}")
+ _LOGGER.info(
+ f"Initialized HA Text AI coordinator with instance: {instance_name}"
+ )
async def _async_update_data(self) -> Dict[str, Any]:
"""Update data via library."""
try:
current_state = self._get_current_state()
- _LOGGER.debug(f"Updating data for {self.instance_name}, current state: {current_state}")
+ _LOGGER.debug(
+ f"Updating data for {self.instance_name}, current state: {current_state}"
+ )
data = {
"state": current_state,
@@ -125,6 +144,7 @@ class HATextAICoordinator(DataUpdateCoordinator):
"system_prompt": self._system_prompt,
"history_size": len(self._conversation_history),
"conversation_history": self._conversation_history,
+ "normalized_name": self.normalized_name,
}
# Validate data
@@ -141,12 +161,15 @@ class HATextAICoordinator(DataUpdateCoordinator):
async def async_update_ha_state(self) -> None:
"""Update Home Assistant state."""
try:
- _LOGGER.debug(f"Requesting state update for {self.instance_name}")
+ _LOGGER.debug(
+ f"Requesting state update for {self.instance_name} (normalized: {self.normalized_name})"
+ )
await self.async_request_refresh()
# Force update of all entities
+ entity_id_base = f"sensor.ha_text_ai_{self.normalized_name.lower()}"
for entity_id in self.hass.states.async_entity_ids():
- if entity_id.startswith(f"sensor.ha_text_ai_{self.instance_name}"):
+ if entity_id.startswith(entity_id_base):
self.hass.states.async_set(entity_id, self._get_current_state())
except Exception as err:
@@ -164,15 +187,67 @@ class HATextAICoordinator(DataUpdateCoordinator):
return STATE_ERROR
return STATE_READY
- def _calculate_context_tokens(self, messages: List[Dict[str, str]]) -> int:
+ def _calculate_context_tokens(self, messages: List[Dict[str, str]], model: str = None) -> int:
+ """
+ Estimate tokens for conversation context.
+
+ Args:
+ messages: List of message dictionaries
+ model: Optional model name for provider-specific estimation
+
+ Returns:
+ Estimated number of tokens
+ """
try:
+ # Anthropic specific token counting
if self.is_anthropic and hasattr(self.client, 'count_tokens'):
return sum(self.client.count_tokens(msg['content']) for msg in messages)
- return sum(len(msg['content']) // 4 for msg in messages)
+ def estimate_tokens(text: str) -> int:
+ """
+ Flexible token estimation algorithm.
+
+ Heuristics:
+ - Count words
+ - Estimate special characters
+ - Fallback to character-based estimation
+ """
+ # Word-based estimation
+ words = len(text.split())
+
+ # Special character handling
+ special_chars = sum(1 for char in text if not char.isalnum())
+
+ # Character-based fallback
+ char_tokens = len(text) // 4
+
+ # Combine estimations with bias towards words
+ total_tokens = (words * 1.5) + (special_chars * 0.5) + char_tokens
+
+ return max(int(total_tokens), words)
+
+ # Calculate total tokens across all messages
+ total_tokens = sum(estimate_tokens(msg['content']) for msg in messages)
+
+ # Logging for debugging
+ _LOGGER.debug(
+ f"Token Estimation: "
+ f"Messages: {len(messages)}, "
+ f"Estimated Tokens: {total_tokens}"
+ )
+
+ return total_tokens
+
except Exception as e:
- _LOGGER.warning(f"Error calculating context tokens: {e}")
- return 0
+ # Safe fallback with detailed logging
+ _LOGGER.warning(
+ f"Token estimation failed. "
+ f"Error: {e}. "
+ f"Using conservative estimation."
+ )
+
+ # Conservative token estimation
+ return len(messages) * 100
async def async_ask_question(
self,
@@ -183,80 +258,133 @@ class HATextAICoordinator(DataUpdateCoordinator):
system_prompt: Optional[str] = None,
context_messages: Optional[int] = None,
) -> dict:
- """Process a question with optional parameters."""
+ """
+ Process a question with optional parameters.
+
+ This method is a direct wrapper around async_process_question,
+ allowing flexible AI interaction with optional model, temperature,
+ and context customization.
+
+ Args:
+ question: The input question or prompt
+ model: Optional AI model to use
+ temperature: Optional response creativity level
+ max_tokens: Optional maximum response length
+ system_prompt: Optional system-level instruction
+ context_messages: Optional number of context messages to include
+
+ Returns:
+ Full response dictionary from the AI
+ """
return await self.async_process_question(
question, model, temperature, max_tokens, system_prompt, context_messages
)
async def async_process_question(
- self,
- question: str,
- model: Optional[str] = None,
- temperature: Optional[float] = None,
- max_tokens: Optional[int] = None,
- system_prompt: Optional[str] = None,
- context_messages: Optional[int] = None,
- ) -> dict:
- temp_context_messages = context_messages or self.context_messages
+ self,
+ question: str,
+ model: Optional[str] = None,
+ temperature: Optional[float] = None,
+ max_tokens: Optional[int] = None,
+ system_prompt: Optional[str] = None,
+ context_messages: Optional[int] = None,
+ ) -> dict:
+ """
+ Enhanced question processing with intelligent token management.
+ """
+ try:
+ self._is_processing = True
+ await self.async_update_ha_state()
- if not question:
- raise ValueError("Question cannot be empty")
+ temp_context_messages = context_messages or self.context_messages
+ temp_model = model or self.model
+ temp_temperature = temperature or self.temperature
+ temp_max_tokens = max_tokens or self.max_tokens
+ temp_system_prompt = system_prompt or self._system_prompt
- _LOGGER.debug(f"Processing question for instance {self.instance_name}")
+ # Start timing
+ start_time = dt_util.utcnow()
- try:
- self._is_processing = True
- await self.async_update_ha_state()
-
- temp_model = model or self.model
- temp_temperature = temperature or self.temperature
- temp_max_tokens = max_tokens or self.max_tokens
- temp_system_prompt = system_prompt or self._system_prompt
-
- start_time = dt_util.utcnow()
-
- messages = []
- if temp_system_prompt:
- if self.is_anthropic:
- system_content = f"\n\nHuman: {temp_system_prompt}\n\nAssistant: I understand and will follow these instructions."
- messages.append({"role": "user", "content": system_content})
- else:
+ # Prepare messages with system prompt
+ messages = []
+ if temp_system_prompt:
messages.append({"role": "system", "content": temp_system_prompt})
- # Add conversation history
- context_history = self._conversation_history[-temp_context_messages:]
- for entry in context_history:
- messages.append({"role": "user", "content": entry["question"]})
- messages.append({"role": "assistant", "content": entry["response"]})
+ # Context history management
+ context_history = self._conversation_history[-temp_context_messages:]
- messages.append({"role": "user", "content": question})
+ # Comprehensive token calculation
+ context_tokens = self._calculate_context_tokens(
+ [{"content": entry["question"]} for entry in context_history] +
+ [{"content": entry["response"]} for entry in context_history] +
+ [{"content": question}],
+ temp_model
+ )
- kwargs = {
- "model": temp_model,
- "temperature": temp_temperature,
- "max_tokens": temp_max_tokens,
- "messages": messages,
- }
+ # Dynamic token allocation
+ available_tokens = max(0, temp_max_tokens - context_tokens)
- response = await self.async_process_message(question, **kwargs)
+ # Context trimming if over token limit
+ if context_tokens > temp_max_tokens:
+ _LOGGER.warning(
+ f"Token limit exceeded. "
+ f"Context: {context_tokens}, "
+ f"Max: {temp_max_tokens}"
+ )
- # Update metrics
- end_time = dt_util.utcnow()
- latency = (end_time - start_time).total_seconds()
- self._update_metrics(latency, response)
+ # Intelligent context reduction
+ while context_tokens > temp_max_tokens // 2 and context_history:
+ context_history.pop(0)
+ context_tokens = self._calculate_context_tokens(
+ [{"content": entry["question"]} for entry in context_history] +
+ [{"content": entry["response"]} for entry in context_history] +
+ [{"content": question}],
+ temp_model
+ )
- # Update history
- self._update_history(question, response)
+ # Rebuild messages with trimmed context
+ for entry in context_history:
+ messages.append({"role": "user", "content": entry["question"]})
+ messages.append({"role": "assistant", "content": entry["response"]})
- return response
+ messages.append({"role": "user", "content": question})
- except Exception as err:
- self._handle_error(err)
- raise HomeAssistantError(f"Failed to process question: {err}")
+ # Detailed token logging
+ _LOGGER.debug(
+ f"Token Analysis: "
+ f"Context Tokens: {context_tokens}, "
+ f"Max Tokens: {temp_max_tokens}, "
+ f"Available Tokens: {available_tokens}"
+ )
- finally:
- self._is_processing = False
- await self.async_update_ha_state()
+ # Prepare API call with dynamic token management
+ kwargs = {
+ "model": temp_model,
+ "temperature": temp_temperature,
+ "max_tokens": min(temp_max_tokens, available_tokens),
+ "messages": messages,
+ }
+
+ # Process message
+ response = await self.async_process_message(question, **kwargs)
+
+ # Update metrics
+ end_time = dt_util.utcnow()
+ latency = (end_time - start_time).total_seconds()
+ self._update_metrics(latency, response)
+
+ # Update history
+ self._update_history(question, response)
+
+ return response
+
+ except Exception as err:
+ self._handle_error(err)
+ raise HomeAssistantError(f"Failed to process question: {err}")
+
+ finally:
+ self._is_processing = False
+ await self.async_update_ha_state()
async def async_process_message(self, question: str, **kwargs) -> dict:
"""Process message using the AI client."""
@@ -272,7 +400,7 @@ class HATextAICoordinator(DataUpdateCoordinator):
"response": response["content"],
"model": kwargs.get("model", self.model),
"instance": self.instance_name,
- "error": None
+ "error": None,
}
return response
@@ -283,20 +411,26 @@ class HATextAICoordinator(DataUpdateCoordinator):
async def _process_anthropic_message(self, question: str, **kwargs) -> dict:
"""Process message using Anthropic API."""
- response = await self.client.messages.create(
- model=kwargs["model"],
- max_tokens=kwargs["max_tokens"],
- messages=kwargs["messages"],
- temperature=kwargs["temperature"],
- )
- return {
- "content": response.content[0].text,
- "tokens": {
- "prompt": response.usage.input_tokens,
- "completion": response.usage.output_tokens,
- "total": response.usage.input_tokens + response.usage.output_tokens
+ try:
+ _LOGGER.debug(f"Anthropic API call: model={kwargs['model']}, max_tokens={kwargs['max_tokens']}")
+ response = await self.client.messages.create(
+ model=kwargs["model"],
+ max_tokens=kwargs["max_tokens"],
+ messages=kwargs["messages"],
+ temperature=kwargs["temperature"],
+ )
+ _LOGGER.debug(f"Anthropic response: tokens={response.usage}")
+ return {
+ "content": response.content[0].text,
+ "tokens": {
+ "prompt": response.usage.input_tokens,
+ "completion": response.usage.output_tokens,
+ "total": response.usage.input_tokens + response.usage.output_tokens,
+ },
}
- }
+ except Exception as e:
+ _LOGGER.error(f"Anthropic API error: {str(e)}")
+ raise
async def _process_openai_message(self, question: str, **kwargs) -> dict:
"""Process message using OpenAI API."""
@@ -313,8 +447,8 @@ class HATextAICoordinator(DataUpdateCoordinator):
"tokens": {
"prompt": response["usage"]["prompt_tokens"],
"completion": response["usage"]["completion_tokens"],
- "total": response["usage"]["total_tokens"]
- }
+ "total": response["usage"]["total_tokens"],
+ },
}
except Exception as e:
_LOGGER.error(f"Error in OpenAI API call: {str(e)}")
@@ -339,29 +473,68 @@ class HATextAICoordinator(DataUpdateCoordinator):
def _update_history(self, question: str, response: dict) -> None:
"""Update conversation history."""
- self._conversation_history.append({
- "timestamp": dt_util.utcnow().isoformat(),
- "question": question,
- "response": response["content"]
- })
+ self._conversation_history.append(
+ {
+ "timestamp": dt_util.utcnow().isoformat(),
+ "question": question,
+ "response": response["content"],
+ }
+ )
while len(self._conversation_history) > self.max_history_size:
self._conversation_history.pop(0)
def _handle_error(self, error: Exception) -> None:
- """Handle error and update metrics."""
+ """
+ Enhanced error handling with comprehensive diagnostics.
+
+ Captures detailed error information, tracks error metrics,
+ and provides context for troubleshooting AI processing issues.
+ """
self._performance_metrics["total_errors"] += 1
self._performance_metrics["failed_requests"] += 1
- self.last_response = {
+ error_details = {
"timestamp": dt_util.utcnow().isoformat(),
- "question": "",
- "response": "",
"model": self.model,
"instance": self.instance_name,
- "error": str(error)
+ "error_message": str(error),
+ "error_type": type(error).__name__,
+ "traceback": traceback.format_exc() if _LOGGER.isEnabledFor(logging.DEBUG) else None,
}
+ # Specific error type handling
+ error_mapping = {
+ HomeAssistantError: {"is_ha_error": True},
+ ConnectionError: {
+ "is_connection_error": True,
+ "is_rate_limited": True
+ },
+ TimeoutError: {"is_timeout": True},
+ PermissionError: {"is_permission_denied": True},
+ ValueError: {"is_validation_error": True}
+ }
+
+ for error_type, error_flags in error_mapping.items():
+ if isinstance(error, error_type):
+ error_details.update(error_flags)
+ break
+
+ # Update system state based on error type
+ if error_details.get("is_rate_limited"):
+ self._is_rate_limited = True
+ _LOGGER.warning(f"Rate limit detected for {self.instance_name}")
+
+ if error_details.get("is_connection_error"):
+ self.endpoint_status = "unavailable"
+
+ self.last_response = error_details
+ _LOGGER.error(f"AI Processing Error: {error_details}")
+
+ # Optional: Add more sophisticated error tracking or notification logic
+ if _LOGGER.isEnabledFor(logging.DEBUG):
+ _LOGGER.debug(f"Full Error Traceback: {error_details['traceback']}")
+
async def async_clear_history(self) -> None:
"""Clear conversation history."""
self._conversation_history = []
@@ -375,3 +548,8 @@ class HATextAICoordinator(DataUpdateCoordinator):
"""Set system prompt."""
self._system_prompt = prompt
await self.async_update_ha_state()
+
+ async def async_shutdown(self) -> None:
+ """Shutdown coordinator."""
+ _LOGGER.debug(f"Shutting down coordinator for {self.instance_name}")
+ self.hass.data[DOMAIN].pop(self.instance_name, None)
diff --git a/custom_components/ha_text_ai/manifest.json b/custom_components/ha_text_ai/manifest.json
index 7770f32..4ce5213 100644
--- a/custom_components/ha_text_ai/manifest.json
+++ b/custom_components/ha_text_ai/manifest.json
@@ -23,6 +23,6 @@
"single_config_entry": false,
"ssdp": [],
"usb": [],
- "version": "2.0.1-alpha",
+ "version": "2.0.2-alpha",
"zeroconf": []
}
diff --git a/custom_components/ha_text_ai/sensor.py b/custom_components/ha_text_ai/sensor.py
index e18fd2f..0e1f2e6 100644
--- a/custom_components/ha_text_ai/sensor.py
+++ b/custom_components/ha_text_ai/sensor.py
@@ -58,25 +58,39 @@ from .const import (
ENTITY_ICON,
ENTITY_ICON_ERROR,
ENTITY_ICON_PROCESSING,
+ DEFAULT_NAME_PREFIX,
+ CONF_MAX_HISTORY_SIZE,
)
from .coordinator import HATextAICoordinator
_LOGGER = logging.getLogger(__name__)
+
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the HA Text AI sensor."""
- coordinator = hass.data[DOMAIN][entry.entry_id]
- instance_name = coordinator.instance_name
+ _LOGGER.debug(f"Starting sensor setup for entry: {entry.entry_id}")
- _LOGGER.debug(f"Setting up sensor with instance: {instance_name}")
+ try:
+ coordinator = hass.data[DOMAIN][entry.entry_id]
+ _LOGGER.debug(f"Found coordinator for entry {entry.entry_id}")
- sensor = HATextAISensor(coordinator, entry)
- async_add_entities([sensor], True)
+ instance_name = coordinator.instance_name
+ _LOGGER.debug(f"Setting up sensor with instance: {instance_name}")
+
+ sensor = HATextAISensor(coordinator, entry)
+ _LOGGER.debug(f"Created sensor instance: {sensor.entity_id}")
+
+ async_add_entities([sensor], True)
+ _LOGGER.debug(f"Added sensor entity: {sensor.entity_id}")
+
+ except Exception as err:
+ _LOGGER.exception(f"Error setting up sensor: {err}")
+ raise
class HATextAISensor(CoordinatorEntity, SensorEntity):
"""HA Text AI Sensor."""
@@ -89,19 +103,30 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
config_entry: ConfigEntry,
) -> None:
"""Initialize the sensor."""
+ _LOGGER.debug(f"Initializing sensor with config entry: {config_entry.data}")
+
super().__init__(coordinator)
self._config_entry = config_entry
self._instance_name = coordinator.instance_name
+ self._normalized_name = coordinator.normalized_name
+
+ _LOGGER.debug(f"Instance name: {self._instance_name}")
+ _LOGGER.debug(f"Normalized name: {self._normalized_name}")
+
self._conversation_history = []
self._system_prompt = None
self._attr_name = f"HA Text AI {self._instance_name}"
- self.entity_id = f"sensor.ha_text_ai_{slugify(self._instance_name)}"
+ self.entity_id = f"sensor.ha_text_ai_{self._normalized_name}"
self._attr_unique_id = f"{config_entry.entry_id}"
+ _LOGGER.debug(f"Created sensor with entity_id: {self.entity_id}")
+ _LOGGER.debug(f"Sensor name: {self._attr_name}")
+ _LOGGER.debug(f"Unique ID: {self._attr_unique_id}")
+
self.entity_description = SensorEntityDescription(
- key=f"ha_text_ai_{self._instance_name}",
+ key=f"ha_text_ai_{self._normalized_name.lower()}",
entity_registry_enabled_default=True,
)
@@ -118,13 +143,15 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._attr_unique_id)},
- name=self._attr_name, # Используем имя сенсора
+ name=self._attr_name,
manufacturer="Community",
model=f"{model} ({api_provider} provider)",
sw_version="1.0.0",
)
- _LOGGER.debug(f"Initialized sensor: {self.entity_id} for instance: {self._instance_name}")
+ _LOGGER.debug(
+ f"Initialized sensor: {self.entity_id} for instance: {self._instance_name}"
+ )
@property
def available(self) -> bool:
@@ -180,11 +207,14 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
data = self.coordinator.data
attributes = {
ATTR_MODEL: self._config_entry.data.get(CONF_MODEL, "Unknown"),
- ATTR_API_PROVIDER: self._config_entry.data.get(CONF_API_PROVIDER, "Unknown"),
+ ATTR_API_PROVIDER: self._config_entry.data.get(
+ CONF_API_PROVIDER, "Unknown"
+ ),
ATTR_API_STATUS: self._current_state,
ATTR_TOTAL_ERRORS: self._error_count,
ATTR_LAST_ERROR: self._last_error,
"instance_name": self._instance_name,
+ "normalized_name": self._normalized_name,
ATTR_SYSTEM_PROMPT: data.get("system_prompt"),
ATTR_IS_PROCESSING: data.get("is_processing", False),
ATTR_IS_RATE_LIMITED: data.get("is_rate_limited", False),
@@ -199,32 +229,40 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
metrics = data.get("metrics", {})
if isinstance(metrics, dict):
self._metrics = metrics
- attributes.update({
- METRIC_TOTAL_TOKENS: metrics.get("total_tokens", 0),
- METRIC_PROMPT_TOKENS: metrics.get("prompt_tokens", 0),
- METRIC_COMPLETION_TOKENS: metrics.get("completion_tokens", 0),
- METRIC_SUCCESSFUL_REQUESTS: metrics.get("successful_requests", 0),
- METRIC_FAILED_REQUESTS: metrics.get("failed_requests", 0),
- METRIC_AVERAGE_LATENCY: metrics.get("average_latency", 0),
- METRIC_MAX_LATENCY: metrics.get("max_latency", 0),
- METRIC_MIN_LATENCY: metrics.get("min_latency", float("inf")),
- })
+ attributes.update(
+ {
+ METRIC_TOTAL_TOKENS: metrics.get("total_tokens", 0),
+ METRIC_PROMPT_TOKENS: metrics.get("prompt_tokens", 0),
+ METRIC_COMPLETION_TOKENS: metrics.get("completion_tokens", 0),
+ METRIC_SUCCESSFUL_REQUESTS: metrics.get(
+ "successful_requests", 0
+ ),
+ METRIC_FAILED_REQUESTS: metrics.get("failed_requests", 0),
+ METRIC_AVERAGE_LATENCY: metrics.get("average_latency", 0),
+ METRIC_MAX_LATENCY: metrics.get("max_latency", 0),
+ METRIC_MIN_LATENCY: metrics.get("min_latency", float("inf")),
+ }
+ )
# Add last response
last_response = data.get("last_response", {})
if isinstance(last_response, dict):
self._last_response = last_response
- attributes.update({
- ATTR_RESPONSE: last_response.get("response", ""),
- ATTR_QUESTION: last_response.get("question", ""),
- "last_model": last_response.get("model", ""),
- "last_timestamp": last_response.get("timestamp", ""),
- "last_error": last_response.get("error"),
- })
+ attributes.update(
+ {
+ ATTR_RESPONSE: last_response.get("response", ""),
+ ATTR_QUESTION: last_response.get("question", ""),
+ "last_model": last_response.get("model", ""),
+ "last_timestamp": last_response.get("timestamp", ""),
+ "last_error": last_response.get("error"),
+ }
+ )
# Add performance metrics if available
if ATTR_PERFORMANCE_METRICS in data:
- attributes[ATTR_PERFORMANCE_METRICS] = data[ATTR_PERFORMANCE_METRICS]
+ attributes[ATTR_PERFORMANCE_METRICS] = data[
+ ATTR_PERFORMANCE_METRICS
+ ]
# Add API version if available
if ATTR_API_VERSION in data:
@@ -288,7 +326,7 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
"Error handling update for %s: %s",
self.entity_id,
err,
- exc_info=True
+ exc_info=True,
)
self.async_write_ha_state()
diff --git a/custom_components/ha_text_ai/services.yaml b/custom_components/ha_text_ai/services.yaml
index b69c734..01272dd 100644
--- a/custom_components/ha_text_ai/services.yaml
+++ b/custom_components/ha_text_ai/services.yaml
@@ -40,15 +40,14 @@ ask_question:
min: 1
max: 20
step: 1
- mode: box
+ mode: slider
model:
name: Model
description: "Select AI model to use (optional, overrides default setting)"
required: false
- selector:
- text:
- multiline: false
+ selector:
+ text: {}
temperature:
name: Temperature
@@ -73,3 +72,98 @@ ask_question:
max: 4096
step: 1
mode: box
+
+clear_history:
+ name: Clear History
+ description: >-
+ Delete all stored questions and responses from the conversation history
+ fields:
+ instance:
+ name: Instance
+ description: Name of the HA Text AI instance to clear history for
+ required: true
+ selector:
+ entity:
+ integration: ha_text_ai
+ domain: sensor
+
+get_history:
+ name: Get History
+ description: Retrieve conversation history with optional filtering and sorting
+ fields:
+ instance:
+ name: Instance
+ description: Name of the HA Text AI instance to get history from
+ required: true
+ selector:
+ entity:
+ integration: ha_text_ai
+ domain: sensor
+
+ limit:
+ name: Limit
+ description: Number of conversations to return (1-100)
+ required: false
+ default: 10
+ selector:
+ number:
+ min: 1
+ max: 100
+ step: 1
+ mode: box
+
+ filter_model:
+ name: Filter Model
+ description: Filter conversations by specific AI model
+ required: false
+ selector:
+ text:
+ multiline: false
+
+ start_date:
+ name: Start Date
+ description: Filter conversations starting from this date/time
+ required: false
+ selector:
+ text:
+ multiline: false
+
+ include_metadata:
+ name: Include Metadata
+ description: Include additional information like tokens used, response time, etc.
+ required: false
+ default: false
+ selector:
+ boolean:
+
+ sort_order:
+ name: Sort Order
+ description: Sort order for results (newest or oldest first)
+ required: false
+ default: newest
+ selector:
+ select:
+ options:
+ - newest
+ - oldest
+
+set_system_prompt:
+ name: Set System Prompt
+ description: Set default system behavior instructions for all future conversations
+ fields:
+ instance:
+ name: Instance
+ description: Name of the HA Text AI instance to set system prompt for
+ required: true
+ selector:
+ entity:
+ integration: ha_text_ai
+ domain: sensor
+
+ prompt:
+ name: System Prompt
+ description: Instructions that define how the AI should behave and respond
+ required: true
+ selector:
+ text:
+ multiline: true
diff --git a/custom_components/ha_text_ai/translations/de.json b/custom_components/ha_text_ai/translations/de.json
index 68b954f..28520cb 100644
--- a/custom_components/ha_text_ai/translations/de.json
+++ b/custom_components/ha_text_ai/translations/de.json
@@ -1,261 +1,266 @@
{
- "config": {
- "step": {
- "provider": {
- "title": "KI-Anbieter auswählen",
- "description": "Wählen Sie den KI-Dienst-Anbieter für diese Instanz",
- "data": {
- "api_provider": "API-Anbieter",
- "context_messages": "Anzahl der zu speichernden Kontextnachrichten (1-20)"
- }
- },
- "user": {
- "title": "HA Text AI Instanz konfigurieren",
- "description": "Richten Sie eine neue KI-Assistenten-Instanz mit Ihrem ausgewählten Anbieter ein",
- "data": {
- "name": "Instanzname (z.B. 'GPT Assistent', 'Claude Helfer')",
- "api_key": "API-Schlüssel für Authentifizierung",
- "model": "Zu verwendendes KI-Modell",
- "temperature": "Antwort-Kreativität (0-2, niedriger = fokussierter)",
- "max_tokens": "Maximale Antwortlänge (1-4096 Token)",
- "api_endpoint": "Benutzerdefinierte API-Endpunkt-URL (optional)",
- "request_interval": "Minimale Zeit zwischen Anfragen (0,1-60 Sekunden)",
- "context_messages": "Anzahl der zu speichernden Kontextnachrichten (1-20)"
- }
- }
- },
- "error": {
- "name_exists": "Eine Instanz mit diesem Namen existiert bereits",
- "invalid_name": "Ungültiger Instanzname",
- "invalid_auth": "Authentifizierung fehlgeschlagen - überprüfen Sie Ihren API-Schlüssel",
- "invalid_api_key": "Ungültiger API-Schlüssel - bitte überprüfen Sie Ihre Anmeldedaten",
- "cannot_connect": "Verbindung zum API-Dienst fehlgeschlagen",
- "invalid_model": "Ausgewähltes Modell ist nicht verfügbar",
- "rate_limit": "Anfragelimit überschritten",
- "context_length": "Kontextlänge überschritten",
- "rate_limit_exceeded": "API-Anfragelimit überschritten",
- "maintenance": "Dienst ist in Wartung",
- "invalid_response": "Ungültige API-Antwort erhalten",
- "api_error": "API-Dienst-Fehler aufgetreten",
- "timeout": "Anfrage-Zeitüberschreitung",
- "invalid_instance": "Ungültige Instanz angegeben",
- "unknown": "Unerwarteter Fehler aufgetreten"
+ "config": {
+ "step": {
+ "provider": {
+ "title": "KI-Anbieter auswählen",
+ "description": "Wählen Sie den KI-Dienstanbieter für diese Instanz aus.",
+ "data": {
+ "api_provider": "API-Anbieter",
+ "context_messages": "Anzahl der zu behaltenden Kontextnachrichten (1-20)"
}
+ },
+ "user": {
+ "title": "HA Text AI-Instanz konfigurieren",
+ "description": "Richten Sie eine neue KI-Assistenteninstanz mit Ihrem ausgewählten Anbieter ein.",
+ "data": {
+ "name": "Instanzname (z. B. 'GPT-Assistent', 'Claude-Helfer')",
+ "api_key": "API-Schlüssel zur Authentifizierung",
+ "model": "Zu verwendendes KI-Modell",
+ "temperature": "Antwortkreativität (0-2, niedriger = fokussierter)",
+ "max_tokens": "Maximale Antwortlänge (1-4096 Token)",
+ "api_endpoint": "Benutzerdefinierte API-Endpunkt-URL (optional)",
+ "api_provider": "API-Anbieter",
+ "request_interval": "Mindestzeit zwischen Anfragen (0,1-60 Sekunden)",
+ "context_messages": "Anzahl der zu behaltenden Kontextnachrichten (1-20)",
+ "max_history_size": "Maximale Größe des Konversationsverlaufs (1-100)"
+ }
+ }
},
- "options": {
- "step": {
- "init": {
- "title": "Instanzeinstellungen aktualisieren",
- "description": "Einstellungen für diese KI-Assistenten-Instanz ändern",
- "data": {
- "model": "KI-Modell",
- "temperature": "Antwort-Kreativität (0-2)",
- "max_tokens": "Maximale Antwortlänge (1-4096)",
- "request_interval": "Minimales Anfragen-Intervall (0,1-60 Sekunden)",
- "context_messages": "Anzahl der vorherigen Nachrichten, die in den Kontext einbezogen werden sollen (1-20)"
-
- }
- }
- }
- },
- "services": {
- "ask_question": {
- "name": "Frage stellen (HA Text AI)",
- "description": "Senden Sie eine Frage an das KI-Modell und erhalten Sie eine detaillierte Antwort. Die Antwort wird in der Gesprächshistorie gespeichert und kann später abgerufen werden.",
- "fields": {
- "instance": {
- "name": "Instanz",
- "description": "Name der zu verwendenden HA Text AI Instanz"
- },
- "question": {
- "name": "Frage",
- "description": "Ihre Frage oder Eingabeaufforderung für den KI-Assistenten"
- },
- "context_messages": {
- "name": "Kontextnachrichten",
- "description": "Anzahl der vorherigen Nachrichten, die in den Kontext einbezogen werden sollen (1-20)"
- },
- "system_prompt": {
- "name": "Systemaufforderung",
- "description": "Optionale Systemaufforderung zur Kontexteinstellung für diese spezifische Frage"
- },
- "model": {
- "name": "Modell",
- "description": "Wählen Sie das zu verwendende KI-Modell (optional, überschreibt Standardeinstellung)"
- },
- "temperature": {
- "name": "Temperatur",
- "description": "Steuert die Antwort-Kreativität (0,0-2,0)"
- },
- "max_tokens": {
- "name": "Max. Token",
- "description": "Maximale Länge der Antwort (1-4096 Token)"
- }
- }
- },
- "clear_history": {
- "name": "Verlauf löschen",
- "description": "Alle gespeicherten Fragen und Antworten aus dem Gesprächsverlauf löschen",
- "fields": {
- "instance": {
- "name": "Instanz",
- "description": "Name der HA Text AI Instanz, für die der Verlauf gelöscht werden soll"
- }
- }
- },
- "get_history": {
- "name": "Verlauf abrufen",
- "description": "Gesprächsverlauf mit optionaler Filterung und Sortierung abrufen",
- "fields": {
- "instance": {
- "name": "Instanz",
- "description": "Name der HA Text AI Instanz, aus der der Verlauf abgerufen werden soll"
- },
- "limit": {
- "name": "Limit",
- "description": "Anzahl der zurückzugebenden Gespräche (1-100)"
- },
- "filter_model": {
- "name": "Modell filtern",
- "description": "Gespräche nach bestimmtem KI-Modell filtern"
- },
- "start_date": {
- "name": "Startdatum",
- "description": "Gespräche ab diesem Datum/Zeitpunkt filtern"
- },
- "include_metadata": {
- "name": "Metadaten einbeziehen",
- "description": "Zusätzliche Informationen wie verwendete Token, Antwortzeit usw. einbeziehen"
- },
- "sort_order": {
- "name": "Sortierreihenfolge",
- "description": "Sortierreihenfolge der Ergebnisse (neueste oder älteste zuerst)"
- }
- }
- },
- "set_system_prompt": {
- "name": "Systemaufforderung festlegen",
- "description": "Standardmäßige Systemverhaltensinstruktionen für alle zukünftigen Gespräche festlegen",
- "fields": {
- "instance": {
- "name": "Instanz",
- "description": "Name der HA Text AI Instanz, für die die Systemaufforderung festgelegt werden soll"
- },
- "prompt": {
- "name": "Systemaufforderung",
- "description": "Anweisungen, die definieren, wie sich die KI verhalten und antworten soll"
- }
- }
- }
- },
- "entity": {
- "sensor": {
- "ha_text_ai": {
- "name": "{name}",
- "state": {
- "ready": "Bereit",
- "processing": "Verarbeitung",
- "error": "Fehler",
- "disconnected": "Getrennt",
- "rate_limited": "Anfragelimit",
- "maintenance": "Wartung",
- "initializing": "Initialisierung",
- "retrying": "Wiederholung",
- "queued": "In Warteschlange"
- },
- "state_attributes": {
- "question": {
- "name": "Letzte Frage"
- },
- "response": {
- "name": "Letzte Antwort"
- },
- "model": {
- "name": "Aktuelles Modell"
- },
- "temperature": {
- "name": "Temperatur"
- },
- "max_tokens": {
- "name": "Max. Token"
- },
- "system_prompt": {
- "name": "Systemaufforderung"
- },
- "response_time": {
- "name": "Letzte Antwortzeit"
- },
- "total_responses": {
- "name": "Gesamte Antworten"
- },
- "error_count": {
- "name": "Fehleranzahl"
- },
- "last_error": {
- "name": "Letzter Fehler"
- },
- "api_status": {
- "name": "API-Status"
- },
- "tokens_used": {
- "name": "Insgesamt verwendete Token"
- },
- "average_response_time": {
- "name": "Durchschnittliche Antwortzeit"
- },
- "last_request_time": {
- "name": "Letzte Anforderungszeit"
- },
- "is_processing": {
- "name": "Verarbeitungsstatus"
- },
- "is_rate_limited": {
- "name": "Status Anfragelimit"
- },
- "is_maintenance": {
- "name": "Wartungsstatus"
- },
- "api_version": {
- "name": "API-Version"
- },
- "endpoint_status": {
- "name": "Endpunktstatus"
- },
- "performance_metrics": {
- "name": "Leistungsmetriken"
- },
- "history_size": {
- "name": "Verlaufsgröße"
- },
- "uptime": {
- "name": "Betriebszeit"
- },
- "total_tokens": {
- "name": "Gesamte Token"
- },
- "prompt_tokens": {
- "name": "Prompt-Token"
- },
- "completion_tokens": {
- "name": "Abschluss-Token"
- },
- "successful_requests": {
- "name": "Erfolgreiche Anfragen"
- },
- "failed_requests": {
- "name": "Fehlgeschlagene Anfragen"
- },
- "average_latency": {
- "name": "Durchschnittliche Latenz"
- },
- "max_latency": {
- "name": "Maximale Latenz"
- },
- "min_latency": {
- "name": "Minimale Latenz"
- }
- }
- }
- }
+ "error": {
+ "name_exists": "Eine Instanz mit diesem Namen existiert bereits",
+ "invalid_name": "Ungültiger Instanzname",
+ "invalid_auth": "Authentifizierung fehlgeschlagen - überprüfen Sie Ihren API-Schlüssel",
+ "invalid_api_key": "Ungültiger API-Schlüssel - überprüfen Sie Ihre Anmeldedaten",
+ "cannot_connect": "Verbindung zum API-Dienst fehlgeschlagen",
+ "invalid_model": "Das ausgewählte Modell ist nicht verfügbar",
+ "rate_limit": "Ratenlimit überschritten",
+ "context_length": "Kontextlänge überschritten",
+ "rate_limit_exceeded": "API-Ratenlimit überschritten",
+ "maintenance": "Dienst befindet sich in der Wartung",
+ "invalid_response": "Ungültige API-Antwort empfangen",
+ "api_error": "Fehler im API-Dienst aufgetreten",
+ "timeout": "Anfrage ist abgelaufen",
+ "invalid_instance": "Ungültige Instanz angegeben",
+ "unknown": "Es ist ein unerwarteter Fehler aufgetreten",
+ "empty": "Name darf nicht leer sein",
+ "invalid_characters": "Name darf nur Buchstaben, Zahlen, Leerzeichen, Unterstriche und Bindestriche enthalten",
+ "name_too_long": "Name darf maximal 50 Zeichen lang sein"
}
+ },
+ "options": {
+ "step": {
+ "init": {
+ "title": "Instanzeinstellungen aktualisieren",
+ "description": "Ändern Sie die Einstellungen für diese KI-Assistenteninstanz.",
+ "data": {
+ "model": "KI-Modell",
+ "temperature": "Antwortkreativität (0-2)",
+ "max_tokens": "Maximale Antwortlänge (1-4096)",
+ "request_interval": "Mindestzeitraum zwischen Anfragen (0,1-60 Sekunden)",
+ "context_messages": "Anzahl der vorherigen Nachrichten, die im Kontext enthalten sein sollen (1-20)",
+ "max_history_size": "Maximale Größe des Konversationsverlaufs (1-100)"
+ }
+ }
+ }
+ },
+ "services": {
+ "ask_question": {
+ "name": "Frage stellen (HA Text AI)",
+ "description": "Senden Sie eine Frage an das KI-Modell und erhalten Sie eine detaillierte Antwort. Die Antwort wird im Konversationsverlauf gespeichert und kann später abgerufen werden.",
+ "fields": {
+ "instance": {
+ "name": "Instanz",
+ "description": "Name der zu verwendenden HA Text AI-Instanz"
+ },
+ "question": {
+ "name": "Frage",
+ "description": "Ihre Frage oder Aufforderung an den KI-Assistenten"
+ },
+ "context_messages": {
+ "name": "Kontextnachrichten",
+ "description": "Anzahl der vorherigen Nachrichten, die im Kontext enthalten sein sollen (1-20)"
+ },
+ "system_prompt": {
+ "name": "Systemprompt",
+ "description": "Optionaler Systemprompt, um den Kontext für diese spezielle Frage festzulegen"
+ },
+ "model": {
+ "name": "Modell",
+ "description": "Wählen Sie das zu verwendende KI-Modell aus (optional, überschreibt die Standardeinstellung)"
+ },
+ "temperature": {
+ "name": "Temperatur",
+ "description": "Steuert die Antwortkreativität (0.0-2.0)"
+ },
+ "max_tokens": {
+ "name": "Max. Token",
+ "description": "Maximale Länge der Antwort (1-4096 Token)"
+ }
+ }
+ },
+ "clear_history": {
+ "name": "Verlauf löschen",
+ "description": "Alle gespeicherten Fragen und Antworten aus dem Konversationsverlauf löschen",
+ "fields": {
+ "instance": {
+ "name": "Instanz",
+ "description": "Name der HA Text AI-Instanz, deren Verlauf gelöscht werden soll"
+ }
+ }
+ },
+ "get_history": {
+ "name": "Verlauf abrufen",
+ "description": "Konversationsverlauf mit optionalem Filtern und Sortieren abrufen",
+ "fields": {
+ "instance": {
+ "name": "Instanz",
+ "description": "Name der HA Text AI-Instanz, von der der Verlauf abgerufen werden soll"
+ },
+ "limit": {
+ "name": "Limit",
+ "description": "Anzahl der zurückzugebenden Konversationen (1-100)"
+ },
+ "filter_model": {
+ "name": "Modell filtern",
+ "description": "Konversationen nach einem bestimmten KI-Modell filtern"
+ },
+ "start_date": {
+ "name": "Startdatum",
+ "description": "Konversationen ab diesem Datum/Uhrzeit filtern"
+ },
+ "include_metadata": {
+ "name": "Metadaten einschließen",
+ "description": "Zusätzliche Informationen wie verwendete Token, Antwortzeit usw. einschließen"
+ },
+ "sort_order": {
+ "name": "Sortierreihenfolge",
+ "description": "Sortierreihenfolge für die Ergebnisse (neueste oder älteste zuerst)"
+ }
+ }
+ },
+ "set_system_prompt": {
+ "name": "Systemprompt festlegen",
+ "description": "Legen Sie Standardanweisungen für das Systemverhalten für alle zukünftigen Konversationen fest",
+ "fields": {
+ "instance": {
+ "name": "Instanz",
+ "description": "Name der HA Text AI-Instanz, für die der Systemprompt festgelegt werden soll"
+ },
+ "prompt": {
+ "name": "Systemprompt",
+ "description": "Anweisungen, die definieren, wie sich die KI verhalten und antworten soll"
+ }
+ }
+ }
+ },
+ "entity": {
+ "sensor": {
+ "ha_text_ai": {
+ "name": "{name}",
+ "state": {
+ "ready": "Bereit",
+ "processing": "Verarbeitung",
+ "error": "Fehler",
+ "disconnected": "Getrennt",
+ "rate_limited": "Ratenlimit",
+ "maintenance": "Wartung",
+ "initializing": "Initialisierung",
+ "retrying": "Wiederholen",
+ "queued": "Warteschlange"
+ },
+ "state_attributes": {
+ "question": {
+ "name": "Letzte Frage"
+ },
+ "response": {
+ "name": "Letzte Antwort"
+ },
+ "model": {
+ "name": "Aktuelles Modell"
+ },
+ "temperature": {
+ "name": "Temperatur"
+ },
+ "max_tokens": {
+ "name": "Max. Token"
+ },
+ "system_prompt": {
+ "name": "Systemprompt"
+ },
+ "response_time": {
+ "name": "Letzte Antwortzeit"
+ },
+ "total_responses": {
+ "name": "Gesamtzahl der Antworten"
+ },
+ "error_count": {
+ "name": "Fehleranzahl"
+ },
+ "last_error": {
+ "name": "Letzter Fehler"
+ },
+ "api_status": {
+ "name": "API-Status"
+ },
+ "tokens_used": {
+ "name": "Verwendete Token insgesamt"
+ },
+ "average_response_time": {
+ "name": "Durchschnittliche Antwortzeit"
+ },
+ "last_request_time": {
+ "name": "Zeitpunkt der letzten Anfrage"
+ },
+ "is_processing": {
+ "name": "Verarbeitungsstatus"
+ },
+ "is_rate_limited": {
+ "name": "Ratenlimit-Status"
+ },
+ "is_maintenance": {
+ "name": "Wartungsstatus"
+ },
+ "api_version": {
+ "name": "API-Version"
+ },
+ "endpoint_status": {
+ "name": "Endpunkt-Status"
+ },
+ "performance_metrics": {
+ "name": "Leistungsmetriken"
+ },
+ "history_size": {
+ "name": "Verlaufsgröße"
+ },
+ "uptime": {
+ "name": "Betriebszeit"
+ },
+ "total_tokens": {
+ "name": "Gesamtzahl der Token"
+ },
+ "prompt_tokens": {
+ "name": "Prompt-Token"
+ },
+ "completion_tokens": {
+ "name": "Completion-Token"
+ },
+ "successful_requests": {
+ "name": "Erfolgreiche Anfragen"
+ },
+ "failed_requests": {
+ "name": "Fehlgeschlagene Anfragen"
+ },
+ "average_latency": {
+ "name": "Durchschnittliche Latenz"
+ },
+ "max_latency": {
+ "name": "Maximale Latenz"
+ },
+ "min_latency": {
+ "name": "Minimale Latenz"
+ }
+ }
+ }
+ }
+ }
}
diff --git a/custom_components/ha_text_ai/translations/en.json b/custom_components/ha_text_ai/translations/en.json
index bd1b825..99a87c5 100644
--- a/custom_components/ha_text_ai/translations/en.json
+++ b/custom_components/ha_text_ai/translations/en.json
@@ -1,260 +1,266 @@
{
- "config": {
- "step": {
- "provider": {
- "title": "Select AI Provider",
- "description": "Choose which AI service provider to use for this instance",
- "data": {
- "api_provider": "API Provider",
- "context_messages": "Number of context messages to retain (1-20)"
- }
- },
- "user": {
- "title": "Configure HA Text AI Instance",
- "description": "Set up a new AI assistant instance with your selected provider",
- "data": {
- "name": "Instance name (e.g., 'GPT Assistant', 'Claude Helper')",
- "api_key": "API key for authentication",
- "model": "AI model to use",
- "temperature": "Response creativity (0-2, lower = more focused)",
- "max_tokens": "Maximum response length (1-4096 tokens)",
- "api_endpoint": "Custom API endpoint URL (optional)",
- "request_interval": "Minimum time between requests (0.1-60 seconds)",
- "context_messages": "Number of context messages to retain (1-20)"
- }
- }
- },
- "error": {
- "name_exists": "An instance with this name already exists",
- "invalid_name": "Invalid instance name",
- "invalid_auth": "Authentication failed - check your API key",
- "invalid_api_key": "Invalid API key - please verify your credentials",
- "cannot_connect": "Failed to connect to API service",
- "invalid_model": "Selected model is not available",
- "rate_limit": "Rate limit exceeded",
- "context_length": "Context length exceeded",
- "rate_limit_exceeded": "API rate limit exceeded",
- "maintenance": "Service is under maintenance",
- "invalid_response": "Invalid API response received",
- "api_error": "API service error occurred",
- "timeout": "Request timed out",
- "invalid_instance": "Invalid instance specified",
- "unknown": "Unexpected error occurred"
+ "config": {
+ "step": {
+ "provider": {
+ "title": "Select AI Provider",
+ "description": "Choose which AI service provider to use for this instance.",
+ "data": {
+ "api_provider": "API Provider",
+ "context_messages": "Number of context messages to retain (1-20)"
}
+ },
+ "user": {
+ "title": "Configure HA Text AI Instance",
+ "description": "Set up a new AI assistant instance with your selected provider.",
+ "data": {
+ "name": "Instance name (e.g., 'GPT Assistant', 'Claude Helper')",
+ "api_key": "API key for authentication",
+ "model": "AI model to use",
+ "temperature": "Response creativity (0-2, lower = more focused)",
+ "max_tokens": "Maximum response length (1-4096 tokens)",
+ "api_endpoint": "Custom API endpoint URL (optional)",
+ "api_provider": "API Provider",
+ "request_interval": "Minimum time between requests (0.1-60 seconds)",
+ "context_messages": "Number of context messages to retain (1-20)",
+ "max_history_size": "Maximum conversation history size (1-100)"
+ }
+ }
},
- "options": {
- "step": {
- "init": {
- "title": "Update Instance Settings",
- "description": "Modify settings for this AI assistant instance",
- "data": {
- "model": "AI model",
- "temperature": "Response creativity (0-2)",
- "max_tokens": "Maximum response length (1-4096)",
- "request_interval": "Minimum request interval (0.1-60 seconds)",
- "context_messages": "Number of previous messages to include in context (1-20)"
- }
- }
- }
- },
- "services": {
- "ask_question": {
- "name": "Ask Question (HA Text AI)",
- "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": {
- "instance": {
- "name": "Instance",
- "description": "Name of the HA Text AI instance to use"
- },
- "question": {
- "name": "Question",
- "description": "Your question or prompt for the AI assistant"
- },
- "context_messages": {
- "name": "Context Messages",
- "description": "Number of previous messages to include in context (1-20)"
- },
- "system_prompt": {
- "name": "System Prompt",
- "description": "Optional system prompt to set context for this specific question"
- },
- "model": {
- "name": "Model",
- "description": "Select AI model to use (optional, overrides default setting)"
- },
- "temperature": {
- "name": "Temperature",
- "description": "Controls response creativity (0.0-2.0)"
- },
- "max_tokens": {
- "name": "Max Tokens",
- "description": "Maximum length of the response (1-4096 tokens)"
- }
- }
- },
- "clear_history": {
- "name": "Clear History",
- "description": "Delete all stored questions and responses from the conversation history",
- "fields": {
- "instance": {
- "name": "Instance",
- "description": "Name of the HA Text AI instance to clear history for"
- }
- }
- },
- "get_history": {
- "name": "Get History",
- "description": "Retrieve conversation history with optional filtering and sorting",
- "fields": {
- "instance": {
- "name": "Instance",
- "description": "Name of the HA Text AI instance to get history from"
- },
- "limit": {
- "name": "Limit",
- "description": "Number of conversations to return (1-100)"
- },
- "filter_model": {
- "name": "Filter Model",
- "description": "Filter conversations by specific AI model"
- },
- "start_date": {
- "name": "Start Date",
- "description": "Filter conversations starting from this date/time"
- },
- "include_metadata": {
- "name": "Include Metadata",
- "description": "Include additional information like tokens used, response time, etc."
- },
- "sort_order": {
- "name": "Sort Order",
- "description": "Sort order for results (newest or oldest first)"
- }
- }
- },
- "set_system_prompt": {
- "name": "Set System Prompt",
- "description": "Set default system behavior instructions for all future conversations",
- "fields": {
- "instance": {
- "name": "Instance",
- "description": "Name of the HA Text AI instance to set system prompt for"
- },
- "prompt": {
- "name": "System Prompt",
- "description": "Instructions that define how the AI should behave and respond"
- }
- }
- }
- },
- "entity": {
- "sensor": {
- "ha_text_ai": {
- "name": "{name}",
- "state": {
- "ready": "Ready",
- "processing": "Processing",
- "error": "Error",
- "disconnected": "Disconnected",
- "rate_limited": "Rate Limited",
- "maintenance": "Maintenance",
- "initializing": "Initializing",
- "retrying": "Retrying",
- "queued": "Queued"
- },
- "state_attributes": {
- "question": {
- "name": "Last Question"
- },
- "response": {
- "name": "Last Response"
- },
- "model": {
- "name": "Current Model"
- },
- "temperature": {
- "name": "Temperature"
- },
- "max_tokens": {
- "name": "Max Tokens"
- },
- "system_prompt": {
- "name": "System Prompt"
- },
- "response_time": {
- "name": "Last Response Time"
- },
- "total_responses": {
- "name": "Total Responses"
- },
- "error_count": {
- "name": "Error Count"
- },
- "last_error": {
- "name": "Last Error"
- },
- "api_status": {
- "name": "API Status"
- },
- "tokens_used": {
- "name": "Total Tokens Used"
- },
- "average_response_time": {
- "name": "Average Response Time"
- },
- "last_request_time": {
- "name": "Last Request Time"
- },
- "is_processing": {
- "name": "Processing Status"
- },
- "is_rate_limited": {
- "name": "Rate Limited Status"
- },
- "is_maintenance": {
- "name": "Maintenance Status"
- },
- "api_version": {
- "name": "API Version"
- },
- "endpoint_status": {
- "name": "Endpoint Status"
- },
- "performance_metrics": {
- "name": "Performance Metrics"
- },
- "history_size": {
- "name": "History Size"
- },
- "uptime": {
- "name": "Uptime"
- },
- "total_tokens": {
- "name": "Total Tokens"
- },
- "prompt_tokens": {
- "name": "Prompt Tokens"
- },
- "completion_tokens": {
- "name": "Completion Tokens"
- },
- "successful_requests": {
- "name": "Successful Requests"
- },
- "failed_requests": {
- "name": "Failed Requests"
- },
- "average_latency": {
- "name": "Average Latency"
- },
- "max_latency": {
- "name": "Maximum Latency"
- },
- "min_latency": {
- "name": "Minimum Latency"
- }
- }
- }
- }
+ "error": {
+ "name_exists": "An instance with this name already exists",
+ "invalid_name": "Invalid instance name",
+ "invalid_auth": "Authentication failed - check your API key",
+ "invalid_api_key": "Invalid API key - please verify your credentials",
+ "cannot_connect": "Failed to connect to API service",
+ "invalid_model": "Selected model is not available",
+ "rate_limit": "Rate limit exceeded",
+ "context_length": "Context length exceeded",
+ "rate_limit_exceeded": "API rate limit exceeded",
+ "maintenance": "Service is under maintenance",
+ "invalid_response": "Invalid API response received",
+ "api_error": "API service error occurred",
+ "timeout": "Request timed out",
+ "invalid_instance": "Invalid instance specified",
+ "unknown": "Unexpected error occurred",
+ "empty": "Name cannot be empty",
+ "invalid_characters": "Name can only contain letters, numbers, spaces, underscores and hyphens",
+ "name_too_long": "Name must be 50 characters or less"
}
+ },
+ "options": {
+ "step": {
+ "init": {
+ "title": "Update Instance Settings",
+ "description": "Modify settings for this AI assistant instance.",
+ "data": {
+ "model": "AI model",
+ "temperature": "Response creativity (0-2)",
+ "max_tokens": "Maximum response length (1-4096)",
+ "request_interval": "Minimum request interval (0.1-60 seconds)",
+ "context_messages": "Number of previous messages to include in context (1-20)",
+ "max_history_size": "Maximum conversation history size (1-100)"
+ }
+ }
+ }
+ },
+ "services": {
+ "ask_question": {
+ "name": "Ask Question (HA Text AI)",
+ "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": {
+ "instance": {
+ "name": "Instance",
+ "description": "Name of the HA Text AI instance to use"
+ },
+ "question": {
+ "name": "Question",
+ "description": "Your question or prompt for the AI assistant"
+ },
+ "context_messages": {
+ "name": "Context Messages",
+ "description": "Number of previous messages to include in context (1-20)"
+ },
+ "system_prompt": {
+ "name": "System Prompt",
+ "description": "Optional system prompt to set context for this specific question"
+ },
+ "model": {
+ "name": "Model",
+ "description": "Select AI model to use (optional, overrides default setting)"
+ },
+ "temperature": {
+ "name": "Temperature",
+ "description": "Controls response creativity (0.0-2.0)"
+ },
+ "max_tokens": {
+ "name": "Max Tokens",
+ "description": "Maximum length of the response (1-4096 tokens)"
+ }
+ }
+ },
+ "clear_history": {
+ "name": "Clear History",
+ "description": "Delete all stored questions and responses from the conversation history",
+ "fields": {
+ "instance": {
+ "name": "Instance",
+ "description": "Name of the HA Text AI instance to clear history for"
+ }
+ }
+ },
+ "get_history": {
+ "name": "Get History",
+ "description": "Retrieve conversation history with optional filtering and sorting",
+ "fields": {
+ "instance": {
+ "name": "Instance",
+ "description": "Name of the HA Text AI instance to get history from"
+ },
+ "limit": {
+ "name": "Limit",
+ "description": "Number of conversations to return (1-100)"
+ },
+ "filter_model": {
+ "name": "Filter Model",
+ "description": "Filter conversations by specific AI model"
+ },
+ "start_date": {
+ "name": "Start Date",
+ "description": "Filter conversations starting from this date/time"
+ },
+ "include_metadata": {
+ "name": "Include Metadata",
+ "description": "Include additional information like tokens used, response time, etc."
+ },
+ "sort_order": {
+ "name": "Sort Order",
+ "description": "Sort order for results (newest or oldest first)"
+ }
+ }
+ },
+ "set_system_prompt": {
+ "name": "Set System Prompt",
+ "description": "Set default system behavior instructions for all future conversations",
+ "fields": {
+ "instance": {
+ "name": "Instance",
+ "description": "Name of the HA Text AI instance to set system prompt for"
+ },
+ "prompt": {
+ "name": "System Prompt",
+ "description": "Instructions that define how the AI should behave and respond"
+ }
+ }
+ }
+ },
+ "entity": {
+ "sensor": {
+ "ha_text_ai": {
+ "name": "{name}",
+ "state": {
+ "ready": "Ready",
+ "processing": "Processing",
+ "error": "Error",
+ "disconnected": "Disconnected",
+ "rate_limited": "Rate Limited",
+ "maintenance": "Maintenance",
+ "initializing": "Initializing",
+ "retrying": "Retrying",
+ "queued": "Queued"
+ },
+ "state_attributes": {
+ "question": {
+ "name": "Last Question"
+ },
+ "response": {
+ "name": "Last Response"
+ },
+ "model": {
+ "name": "Current Model"
+ },
+ "temperature": {
+ "name": "Temperature"
+ },
+ "max_tokens": {
+ "name": "Max Tokens"
+ },
+ "system_prompt": {
+ "name": "System Prompt"
+ },
+ "response_time": {
+ "name": "Last Response Time"
+ },
+ "total_responses": {
+ "name": "Total Responses"
+ },
+ "error_count": {
+ "name": "Error Count"
+ },
+ "last_error": {
+ "name": "Last Error"
+ },
+ "api_status": {
+ "name": "API Status"
+ },
+ "tokens_used": {
+ "name": "Total Tokens Used"
+ },
+ "average_response_time": {
+ "name": "Average Response Time"
+ },
+ "last_request_time": {
+ "name": "Last Request Time"
+ },
+ "is_processing": {
+ "name": "Processing Status"
+ },
+ "is_rate_limited": {
+ "name": "Rate Limited Status"
+ },
+ "is_maintenance": {
+ "name": "Maintenance Status"
+ },
+ "api_version": {
+ "name": "API Version"
+ },
+ "endpoint_status": {
+ "name": "Endpoint Status"
+ },
+ "performance_metrics": {
+ "name": "Performance Metrics"
+ },
+ "history_size": {
+ "name": "History Size"
+ },
+ "uptime": {
+ "name": "Uptime"
+ },
+ "total_tokens": {
+ "name": "Total Tokens"
+ },
+ "prompt_tokens": {
+ "name": "Prompt Tokens"
+ },
+ "completion_tokens": {
+ "name": "Completion Tokens"
+ },
+ "successful_requests": {
+ "name": "Successful Requests"
+ },
+ "failed_requests": {
+ "name": "Failed Requests"
+ },
+ "average_latency": {
+ "name": "Average Latency"
+ },
+ "max_latency": {
+ "name": "Maximum Latency"
+ },
+ "min_latency": {
+ "name": "Minimum Latency"
+ }
+ }
+ }
+ }
+ }
}
diff --git a/custom_components/ha_text_ai/translations/ru.json b/custom_components/ha_text_ai/translations/ru.json
index 30ca1ea..906afd2 100644
--- a/custom_components/ha_text_ai/translations/ru.json
+++ b/custom_components/ha_text_ai/translations/ru.json
@@ -1,260 +1,266 @@
{
- "config": {
- "step": {
- "provider": {
- "title": "Выбор провайдера ИИ",
- "description": "Выберите сервис ИИ для этого экземпляра",
- "data": {
- "api_provider": "Провайдер API",
- "context_messages": "Количество сообщений в контексте (1-20)"
- }
- },
- "user": {
- "title": "Настройка экземпляра HA Text AI",
- "description": "Настройте нового помощника ИИ с выбранным провайдером",
- "data": {
- "name": "Название экземпляра (например, 'GPT Помощник', 'Claude Ассистент')",
- "api_key": "API-ключ для аутентификации",
- "model": "Модель ИИ для использования",
- "temperature": "Креативность ответов (0-2, меньше = более сфокусированно)",
- "max_tokens": "Максимальная длина ответа (1-4096 токенов)",
- "api_endpoint": "Пользовательский URL-адрес API (необязательно)",
- "request_interval": "Минимальный интервал между запросами (0.1-60 секунд)",
- "context_messages": "Количество сохраняемых контекстных сообщений (1-20)"
- }
- }
- },
- "error": {
- "name_exists": "Экземпляр с таким именем уже существует",
- "invalid_name": "Некорректное имя экземпляра",
- "invalid_auth": "Ошибка аутентификации - проверьте API-ключ",
- "invalid_api_key": "Неверный API-ключ - проверьте учетные данные",
- "cannot_connect": "Не удалось подключиться к сервису API",
- "invalid_model": "Выбранная модель недоступна",
- "rate_limit": "Превышен лимит запросов",
- "context_length": "Превышена длина контекста",
- "rate_limit_exceeded": "Превышен лимит API",
- "maintenance": "Сервис на техническом обслуживании",
- "invalid_response": "Получен некорректный ответ API",
- "api_error": "Произошла ошибка сервиса API",
- "timeout": "Время ожидания истекло",
- "invalid_instance": "Указан неверный экземпляр",
- "unknown": "Произошла непредвиденная ошибка"
+ "config": {
+ "step": {
+ "provider": {
+ "title": "Выбор поставщика ИИ",
+ "description": "Выберите поставщика услуг ИИ для этой инстанции.",
+ "data": {
+ "api_provider": "Поставщик API",
+ "context_messages": "Количество контекстных сообщений для сохранения (1-20)"
}
+ },
+ "user": {
+ "title": "Настройка инстанции HA Text AI",
+ "description": "Настройте новую инстанцию помощника ИИ с выбранным вами поставщиком.",
+ "data": {
+ "name": "Имя инстанции (например, 'GPT Assistant', 'Claude Helper')",
+ "api_key": "API-ключ для аутентификации",
+ "model": "Используемая модель ИИ",
+ "temperature": "Креативность ответа (0-2, чем ниже, тем больше фокусировки)",
+ "max_tokens": "Максимальная длина ответа (1-4096 токенов)",
+ "api_endpoint": "Пользовательский URL-адрес конечной точки API (необязательно)",
+ "api_provider": "Поставщик API",
+ "request_interval": "Минимальное время между запросами (0,1-60 секунд)",
+ "context_messages": "Количество контекстных сообщений для сохранения (1-20)",
+ "max_history_size": "Максимальный размер истории разговора (1-100)"
+ }
+ }
},
- "options": {
- "step": {
- "init": {
- "title": "Обновление настроек экземпляра",
- "description": "Измените настройки для этого помощника ИИ",
- "data": {
- "model": "Модель ИИ",
- "temperature": "Креативность ответов (0-2)",
- "max_tokens": "Максимальная длина ответа (1-4096)",
- "request_interval": "Минимальный интервал запросов (0.1-60 секунд)",
- "context_messages": "Количество предыдущих сообщений для включения в контекст (1-20)"
- }
- }
- }
- },
- "services": {
- "ask_question": {
- "name": "Задать вопрос (HA Text AI)",
- "description": "Отправьте вопрос модели ИИ и получите подробный ответ. Ответ будет сохранен в истории беседы и может быть извлечен позже.",
- "fields": {
- "instance": {
- "name": "Экземпляр",
- "description": "Название экземпляра HA Text AI для использования"
- },
- "question": {
- "name": "Вопрос",
- "description": "Ваш вопрос или запрос помощнику ИИ"
- },
- "context_messages": {
- "name": "Контекстные сообщения",
- "description": "Количество предыдущих сообщений для включения в контекст (1-20)"
- },
- "system_prompt": {
- "name": "Системный промпт",
- "description": "Необязательный системный промпт для установки контекста для этого конкретного вопроса"
- },
- "model": {
- "name": "Модель",
- "description": "Выберите модель ИИ (необязательно, переопределяет настройки по умолчанию)"
- },
- "temperature": {
- "name": "Температура",
- "description": "Управляет креативностью ответа (0.0-2.0)"
- },
- "max_tokens": {
- "name": "Максимальное количество токенов",
- "description": "Максимальная длина ответа (1-4096 токенов)"
- }
- }
- },
- "clear_history": {
- "name": "Очистить историю",
- "description": "Удалить все сохраненные вопросы и ответы из истории беседы",
- "fields": {
- "instance": {
- "name": "Экземпляр",
- "description": "Название экземпляра HA Text AI для очистки истории"
- }
- }
- },
- "get_history": {
- "name": "Получить историю",
- "description": "Получить историю беседы с дополнительной фильтрацией и сортировкой",
- "fields": {
- "instance": {
- "name": "Экземпляр",
- "description": "Название экземпляра HA Text AI для получения истории"
- },
- "limit": {
- "name": "Лимит",
- "description": "Количество возвращаемых бесед (1-100)"
- },
- "filter_model": {
- "name": "Фильтр модели",
- "description": "Фильтрация бесед по конкретной модели ИИ"
- },
- "start_date": {
- "name": "Начальная дата",
- "description": "Фильтрация бесед, начиная с указанной даты/времени"
- },
- "include_metadata": {
- "name": "Включить метаданные",
- "description": "Включить дополнительную информацию, например, использованные токены, время ответа и т.д."
- },
- "sort_order": {
- "name": "Порядок сортировки",
- "description": "Порядок результатов (сначала новые или старые)"
- }
- }
- },
- "set_system_prompt": {
- "name": "Установить системный промпт",
- "description": "Установить инструкции по умолчанию для поведения ИИ во всех будущих разговорах",
- "fields": {
- "instance": {
- "name": "Экземпляр",
- "description": "Название экземпляра HA Text AI для установки системного промпта"
- },
- "prompt": {
- "name": "Системный промпт",
- "description": "Инструкции, определяющие, как ИИ должен вести себя и отвечать"
- }
- }
- }
- },
- "entity": {
- "sensor": {
- "ha_text_ai": {
- "name": "{name}",
- "state": {
- "ready": "Готов",
- "processing": "Обработка",
- "error": "Ошибка",
- "disconnected": "Отключен",
- "rate_limited": "Лимит запросов",
- "maintenance": "Техническое обслуживание",
- "initializing": "Инициализация",
- "retrying": "Повторная попытка",
- "queued": "В очереди"
- },
- "state_attributes": {
- "question": {
- "name": "Последний вопрос"
- },
- "response": {
- "name": "Последний ответ"
- },
- "model": {
- "name": "Текущая модель"
- },
- "temperature": {
- "name": "Температура"
- },
- "max_tokens": {
- "name": "Максимальное количество токенов"
- },
- "system_prompt": {
- "name": "Системный промпт"
- },
- "response_time": {
- "name": "Время последнего ответа"
- },
- "total_responses": {
- "name": "Всего ответов"
- },
- "error_count": {
- "name": "Количество ошибок"
- },
- "last_error": {
- "name": "Последняя ошибка"
- },
- "api_status": {
- "name": "Статус API"
- },
- "tokens_used": {
- "name": "Всего использовано токенов"
- },
- "average_response_time": {
- "name": "Среднее время ответа"
- },
- "last_request_time": {
- "name": "Время последнего запроса"
- },
- "is_processing": {
- "name": "Статус обработки"
- },
- "is_rate_limited": {
- "name": "Статус лимита запросов"
- },
- "is_maintenance": {
- "name": "Статус обслуживания"
- },
- "api_version": {
- "name": "Версия API"
- },
- "endpoint_status": {
- "name": "Статус эндпоинта"
- },
- "performance_metrics": {
- "name": "Показатели производительности"
- },
- "history_size": {
- "name": "Размер истории"
- },
- "uptime": {
- "name": "Время работы"
- },
- "total_tokens": {
- "name": "Всего токенов"
- },
- "prompt_tokens": {
- "name": "Токены промпта"
- },
- "completion_tokens": {
- "name": "Токены завершения"
- },
- "successful_requests": {
- "name": "Успешные запросы"
- },
- "failed_requests": {
- "name": "Неудачные запросы"
- },
- "average_latency": {
- "name": "Средняя задержка"
- },
- "max_latency": {
- "name": "Максимальная задержка"
- },
- "min_latency": {
- "name": "Минимальная задержка"
- }
- }
- }
- }
+ "error": {
+ "name_exists": "Инстанция с таким именем уже существует",
+ "invalid_name": "Некорректное имя инстанции",
+ "invalid_auth": "Аутентификация не удалась - проверьте ваш API-ключ",
+ "invalid_api_key": "Неверный API-ключ - проверьте ваши учетные данные",
+ "cannot_connect": "Не удалось подключиться к службе API",
+ "invalid_model": "Выбранная модель недоступна",
+ "rate_limit": "Превышен лимит запросов",
+ "context_length": "Превышена длина контекста",
+ "rate_limit_exceeded": "Превышен лимит запросов API",
+ "maintenance": "Сервис находится на техническом обслуживании",
+ "invalid_response": "Получен неверный ответ API",
+ "api_error": "Произошла ошибка службы API",
+ "timeout": "Запрос превысил время ожидания",
+ "invalid_instance": "Указана неверная инстанция",
+ "unknown": "Произошла непредвиденная ошибка",
+ "empty": "Имя не может быть пустым",
+ "invalid_characters": "Имя может содержать только буквы, цифры, пробелы, подчеркивания и дефисы",
+ "name_too_long": "Имя должно быть не более 50 символов"
}
+ },
+ "options": {
+ "step": {
+ "init": {
+ "title": "Обновление настроек инстанции",
+ "description": "Измените настройки для этой инстанции помощника ИИ.",
+ "data": {
+ "model": "Модель ИИ",
+ "temperature": "Креативность ответа (0-2)",
+ "max_tokens": "Максимальная длина ответа (1-4096)",
+ "request_interval": "Минимальный интервал между запросами (0,1-60 секунд)",
+ "context_messages": "Количество предыдущих сообщений для включения в контекст (1-20)",
+ "max_history_size": "Максимальный размер истории разговора (1-100)"
+ }
+ }
+ }
+ },
+ "services": {
+ "ask_question": {
+ "name": "Задать вопрос (HA Text AI)",
+ "description": "Отправьте вопрос модели ИИ и получите подробный ответ. Ответ будет сохранен в истории разговора и может быть извлечен позже.",
+ "fields": {
+ "instance": {
+ "name": "Инстанция",
+ "description": "Имя используемой инстанции HA Text AI"
+ },
+ "question": {
+ "name": "Вопрос",
+ "description": "Ваш вопрос или запрос к помощнику ИИ"
+ },
+ "context_messages": {
+ "name": "Контекстные сообщения",
+ "description": "Количество предыдущих сообщений для включения в контекст (1-20)"
+ },
+ "system_prompt": {
+ "name": "Системный запрос",
+ "description": "Необязательный системный запрос для установки контекста для этого конкретного вопроса"
+ },
+ "model": {
+ "name": "Модель",
+ "description": "Выберите модель ИИ для использования (необязательно, переопределяет настройку по умолчанию)"
+ },
+ "temperature": {
+ "name": "Температура",
+ "description": "Управляет креативностью ответа (0.0-2.0)"
+ },
+ "max_tokens": {
+ "name": "Макс. токенов",
+ "description": "Максимальная длина ответа (1-4096 токенов)"
+ }
+ }
+ },
+ "clear_history": {
+ "name": "Очистить историю",
+ "description": "Удалить все сохраненные вопросы и ответы из истории разговора",
+ "fields": {
+ "instance": {
+ "name": "Инстанция",
+ "description": "Имя инстанции HA Text AI, для которой нужно очистить историю"
+ }
+ }
+ },
+ "get_history": {
+ "name": "Получить историю",
+ "description": "Извлечь историю разговора с возможностью фильтрации и сортировки",
+ "fields": {
+ "instance": {
+ "name": "Инстанция",
+ "description": "Имя инстанции HA Text AI, из которой нужно получить историю"
+ },
+ "limit": {
+ "name": "Лимит",
+ "description": "Количество разговоров для возврата (1-100)"
+ },
+ "filter_model": {
+ "name": "Фильтр модели",
+ "description": "Фильтрация разговоров по определенной модели ИИ"
+ },
+ "start_date": {
+ "name": "Дата начала",
+ "description": "Фильтрация разговоров, начиная с этой даты/времени"
+ },
+ "include_metadata": {
+ "name": "Включить метаданные",
+ "description": "Включить дополнительную информацию, такую как использованные токены, время ответа и т.д."
+ },
+ "sort_order": {
+ "name": "Порядок сортировки",
+ "description": "Порядок сортировки результатов (самые новые или самые старые)"
+ }
+ }
+ },
+ "set_system_prompt": {
+ "name": "Установить системный запрос",
+ "description": "Установить инструкции по умолчанию для поведения системы для всех будущих разговоров",
+ "fields": {
+ "instance": {
+ "name": "Инстанция",
+ "description": "Имя инстанции HA Text AI, для которой нужно установить системный запрос"
+ },
+ "prompt": {
+ "name": "Системный запрос",
+ "description": "Инструкции, определяющие, как ИИ должен себя вести и отвечать"
+ }
+ }
+ }
+ },
+ "entity": {
+ "sensor": {
+ "ha_text_ai": {
+ "name": "{name}",
+ "state": {
+ "ready": "Готов",
+ "processing": "Обработка",
+ "error": "Ошибка",
+ "disconnected": "Отключен",
+ "rate_limited": "Лимит запросов",
+ "maintenance": "Техническое обслуживание",
+ "initializing": "Инициализация",
+ "retrying": "Повторная попытка",
+ "queued": "В очереди"
+ },
+ "state_attributes": {
+ "question": {
+ "name": "Последний вопрос"
+ },
+ "response": {
+ "name": "Последний ответ"
+ },
+ "model": {
+ "name": "Текущая модель"
+ },
+ "temperature": {
+ "name": "Температура"
+ },
+ "max_tokens": {
+ "name": "Макс. токенов"
+ },
+ "system_prompt": {
+ "name": "Системный запрос"
+ },
+ "response_time": {
+ "name": "Время последнего ответа"
+ },
+ "total_responses": {
+ "name": "Всего ответов"
+ },
+ "error_count": {
+ "name": "Количество ошибок"
+ },
+ "last_error": {
+ "name": "Последняя ошибка"
+ },
+ "api_status": {
+ "name": "Статус API"
+ },
+ "tokens_used": {
+ "name": "Использовано токенов всего"
+ },
+ "average_response_time": {
+ "name": "Среднее время ответа"
+ },
+ "last_request_time": {
+ "name": "Время последнего запроса"
+ },
+ "is_processing": {
+ "name": "Статус обработки"
+ },
+ "is_rate_limited": {
+ "name": "Статус ограничения запросов"
+ },
+ "is_maintenance": {
+ "name": "Статус технического обслуживания"
+ },
+ "api_version": {
+ "name": "Версия API"
+ },
+ "endpoint_status": {
+ "name": "Статус конечной точки"
+ },
+ "performance_metrics": {
+ "name": "Метрики производительности"
+ },
+ "history_size": {
+ "name": "Размер истории"
+ },
+ "uptime": {
+ "name": "Время работы"
+ },
+ "total_tokens": {
+ "name": "Всего токенов"
+ },
+ "prompt_tokens": {
+ "name": "Токены запроса"
+ },
+ "completion_tokens": {
+ "name": "Токены завершения"
+ },
+ "successful_requests": {
+ "name": "Успешных запросов"
+ },
+ "failed_requests": {
+ "name": "Неудачных запросов"
+ },
+ "average_latency": {
+ "name": "Средняя задержка"
+ },
+ "max_latency": {
+ "name": "Максимальная задержка"
+ },
+ "min_latency": {
+ "name": "Минимальная задержка"
+ }
+ }
+ }
+ }
+ }
}