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 - -
- -![GitHub release](https://img.shields.io/github/release/smkrv/ha-text-ai.svg?style=flat-square) ![GitHub last commit](https://img.shields.io/github/last-commit/smkrv/ha-text-ai.svg?style=flat-square) [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg?style=flat-square)](https://opensource.org/licenses/MIT) [![hacs_badge](https://img.shields.io/badge/HACS-Custom-41BDF5.svg?style=flat-square)](https://github.com/hacs/integration) ![/README.md](https://img.shields.io/badge/language-English-green?style=flat-square) ![/README_RU.md](https://img.shields.io/badge/language-Russian-green?style=flat-square) ![](https://img.shields.io/badge/language-Deutch-green?style=flat-square) - - HA Text AI - -### Расширенная 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": "Минимальная задержка" + } + } + } + } + } }