mirror of
https://github.com/smkrv/ha-text-ai.git
synced 2026-07-22 23:24:03 +08:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d6144be7ed | ||
|
|
9afbb904b3 | ||
|
|
93558b2444 | ||
|
|
9f93f1ee18 | ||
|
|
30a9b53ba1 | ||
|
|
5175970d55 | ||
|
|
f6bfbd4a07 | ||
|
|
4ddb0dc977 | ||
|
|
24dc4ac4d4 |
@@ -9,6 +9,8 @@ from homeassistant.exceptions import ConfigEntryNotReady
|
||||
from homeassistant.helpers import aiohttp_client
|
||||
from homeassistant.helpers import config_validation as cv
|
||||
|
||||
from .coordinator import HATextAICoordinator
|
||||
|
||||
from .const import (
|
||||
DOMAIN,
|
||||
PLATFORMS,
|
||||
|
||||
@@ -62,24 +62,6 @@ STEP_USER_DATA_SCHEMA = vol.Schema({
|
||||
),
|
||||
})
|
||||
|
||||
async def validate_endpoint(endpoint: str) -> Tuple[bool, str]:
|
||||
"""Validate API endpoint accessibility."""
|
||||
try:
|
||||
parsed_url = urlparse(endpoint)
|
||||
if parsed_url.scheme not in ('http', 'https'):
|
||||
return False, "invalid_endpoint_scheme"
|
||||
|
||||
connector = aiohttp.TCPConnector(ssl=SSL_CONTEXT)
|
||||
async with timeout(5):
|
||||
async with aiohttp.ClientSession(connector=connector) as session:
|
||||
async with session.get(endpoint) as response:
|
||||
if response.status != 200:
|
||||
return False, "endpoint_not_available"
|
||||
return True, ""
|
||||
except Exception as e:
|
||||
_LOGGER.error("Error validating endpoint: %s", str(e))
|
||||
return False, "endpoint_error"
|
||||
|
||||
async def validate_api_connection(
|
||||
api_key: str,
|
||||
endpoint: str,
|
||||
@@ -87,64 +69,56 @@ async def validate_api_connection(
|
||||
retry_count: int = 3,
|
||||
retry_delay: float = 1.0
|
||||
) -> Tuple[bool, str, list]:
|
||||
"""Validate API connection with improved retry logic."""
|
||||
# Validate endpoint first
|
||||
endpoint_valid, endpoint_error = await validate_endpoint(endpoint)
|
||||
if not endpoint_valid:
|
||||
return False, endpoint_error, []
|
||||
|
||||
connector = aiohttp.TCPConnector(ssl=SSL_CONTEXT)
|
||||
async with aiohttp.ClientSession(connector=connector) as session:
|
||||
for attempt in range(retry_count):
|
||||
try:
|
||||
async with timeout(10):
|
||||
client = AsyncOpenAI(
|
||||
api_key=api_key,
|
||||
base_url=endpoint,
|
||||
http_client=session
|
||||
)
|
||||
|
||||
models = await client.models.list()
|
||||
model_ids = [model.id for model in models.data]
|
||||
|
||||
if model not in model_ids:
|
||||
_LOGGER.warning(
|
||||
"Model %s not found in available models: %s",
|
||||
model,
|
||||
", ".join(model_ids)
|
||||
)
|
||||
return False, "invalid_model", model_ids
|
||||
return True, "", model_ids
|
||||
|
||||
except asyncio.TimeoutError:
|
||||
_LOGGER.warning(
|
||||
"Timeout during API validation (attempt %d/%d)",
|
||||
attempt + 1,
|
||||
retry_count
|
||||
"""Validate API connection with retry logic."""
|
||||
for attempt in range(retry_count):
|
||||
try:
|
||||
async with timeout(10):
|
||||
client = AsyncOpenAI(
|
||||
api_key=api_key,
|
||||
base_url=endpoint,
|
||||
)
|
||||
if attempt == retry_count - 1:
|
||||
return False, "timeout", []
|
||||
await asyncio.sleep(retry_delay)
|
||||
|
||||
except AuthenticationError as err:
|
||||
_LOGGER.error("Authentication error: %s", str(err))
|
||||
return False, "invalid_auth", []
|
||||
models = await client.models.list()
|
||||
model_ids = [model.id for model in models.data]
|
||||
|
||||
except RateLimitError as err:
|
||||
_LOGGER.error("Rate limit exceeded: %s", str(err))
|
||||
return False, "rate_limit", []
|
||||
if model not in model_ids:
|
||||
_LOGGER.warning(
|
||||
"Model %s not found in available models: %s",
|
||||
model,
|
||||
", ".join(model_ids)
|
||||
)
|
||||
return False, "invalid_model", model_ids
|
||||
return True, "", model_ids
|
||||
|
||||
except APIConnectionError as err:
|
||||
_LOGGER.error("API connection error: %s", str(err))
|
||||
return False, "cannot_connect", []
|
||||
except asyncio.TimeoutError:
|
||||
_LOGGER.warning(
|
||||
"Timeout during API validation (attempt %d/%d)",
|
||||
attempt + 1,
|
||||
retry_count
|
||||
)
|
||||
if attempt == retry_count - 1:
|
||||
return False, "timeout", []
|
||||
await asyncio.sleep(retry_delay)
|
||||
|
||||
except APIError as err:
|
||||
_LOGGER.error("API error: %s", str(err))
|
||||
return False, "api_error", []
|
||||
except AuthenticationError as err:
|
||||
_LOGGER.error("Authentication error: %s", str(err))
|
||||
return False, "invalid_auth", []
|
||||
|
||||
except Exception as err:
|
||||
_LOGGER.exception("Unexpected error during validation: %s", str(err))
|
||||
return False, "unknown", []
|
||||
except RateLimitError as err:
|
||||
_LOGGER.error("Rate limit exceeded: %s", str(err))
|
||||
return False, "rate_limit", []
|
||||
|
||||
except APIConnectionError as err:
|
||||
_LOGGER.error("API connection error: %s", str(err))
|
||||
return False, "cannot_connect", []
|
||||
|
||||
except APIError as err:
|
||||
_LOGGER.error("API error: %s", str(err))
|
||||
return False, "api_error", []
|
||||
|
||||
except Exception as err:
|
||||
_LOGGER.exception("Unexpected error during validation: %s", str(err))
|
||||
return False, "unknown", []
|
||||
|
||||
class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||
"""Handle a config flow for HA text AI."""
|
||||
|
||||
@@ -9,6 +9,6 @@
|
||||
"issue_tracker": "https://github.com/smkrv/ha-text-ai/issues",
|
||||
"requirements": ["openai>=1.0.0"],
|
||||
"ssdp": [],
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.10",
|
||||
"zeroconf": []
|
||||
}
|
||||
|
||||
@@ -131,12 +131,7 @@ get_history:
|
||||
mode: dropdown
|
||||
|
||||
set_system_prompt:
|
||||
name: Set System Prompt
|
||||
description: >-
|
||||
Configure the AI's behavior by setting a system prompt.
|
||||
This affects how the AI interprets and responds to all future questions.
|
||||
The prompt will persist until changed or cleared.
|
||||
fields:
|
||||
fields:
|
||||
prompt:
|
||||
name: System Prompt
|
||||
description: >-
|
||||
@@ -157,14 +152,3 @@ set_system_prompt:
|
||||
text:
|
||||
multiline: true
|
||||
type: text
|
||||
max_length: 1000
|
||||
|
||||
clear_prompt:
|
||||
name: Clear Existing Prompt
|
||||
description: >-
|
||||
Set to true to remove the current system prompt before applying the new one.
|
||||
This ensures no conflicting instructions remain.
|
||||
required: false
|
||||
default: false
|
||||
selector:
|
||||
boolean: {}
|
||||
|
||||
@@ -1,61 +1,54 @@
|
||||
{
|
||||
"config": {
|
||||
"option": {
|
||||
"api_key": {
|
||||
"name": "API Key",
|
||||
"description": "Your OpenAI API key"
|
||||
},
|
||||
"model": {
|
||||
"name": "Model",
|
||||
"description": "AI model to use for responses"
|
||||
},
|
||||
"temperature": {
|
||||
"name": "Temperature",
|
||||
"description": "Temperature for response generation (0-2)"
|
||||
},
|
||||
"max_tokens": {
|
||||
"name": "Max Tokens",
|
||||
"description": "Maximum tokens in response (1-4096)"
|
||||
},
|
||||
"api_endpoint": {
|
||||
"name": "API Endpoint",
|
||||
"description": "API endpoint URL"
|
||||
},
|
||||
"request_interval": {
|
||||
"name": "Request Interval",
|
||||
"description": "Minimum time between API requests (seconds)"
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Set up HA Text AI",
|
||||
"description": "Configure your OpenAI integration",
|
||||
"data": {
|
||||
"api_key": "Your OpenAI API key",
|
||||
"model": "AI model to use for responses",
|
||||
"temperature": "Temperature for response generation (0-2)",
|
||||
"max_tokens": "Maximum tokens in response (1-4096)",
|
||||
"api_endpoint": "API endpoint URL",
|
||||
"request_interval": "Minimum time between API requests (seconds)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"temperature": "Temperature",
|
||||
"max_tokens": "Max Tokens",
|
||||
"request_interval": "Request Interval"
|
||||
},
|
||||
"error": {
|
||||
"invalid_api_key": "Invalid API key",
|
||||
"cannot_connect": "Cannot connect to the API",
|
||||
"unknown_error": "Unknown error",
|
||||
"invalid_model": "Invalid model",
|
||||
"rate_limit_exceeded": "Rate limit exceeded",
|
||||
"context_length_exceeded": "Context length exceeded",
|
||||
"api_error": "API error",
|
||||
"timeout_error": "Timeout error",
|
||||
"queue_full": "Queue full",
|
||||
"invalid_prompt": "Invalid prompt"
|
||||
},
|
||||
"state": {
|
||||
"ready": "Ready",
|
||||
"processing": "Processing",
|
||||
"error": "Error",
|
||||
"disconnected": "Disconnected",
|
||||
"rate_limited": "Rate limited",
|
||||
"initializing": "Initializing"
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "HA Text AI Options",
|
||||
"data": {
|
||||
"temperature": "Response temperature (0-2)",
|
||||
"max_tokens": "Maximum response length",
|
||||
"request_interval": "Time between requests"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"ask_question": "Ask Question",
|
||||
"clear_history": "Clear History",
|
||||
"get_history": "Get History",
|
||||
"set_system_prompt": "Set System Prompt"
|
||||
"ask_question": {
|
||||
"name": "Ask Question",
|
||||
"description": "Send a question to the AI model",
|
||||
"fields": {
|
||||
"question": {
|
||||
"name": "Question",
|
||||
"description": "Your question for the AI"
|
||||
}
|
||||
}
|
||||
},
|
||||
"clear_history": {
|
||||
"name": "Clear History",
|
||||
"description": "Clear conversation history"
|
||||
},
|
||||
"get_history": {
|
||||
"name": "Get History",
|
||||
"description": "Retrieve conversation history"
|
||||
},
|
||||
"set_system_prompt": {
|
||||
"name": "Set System Prompt",
|
||||
"description": "Set system behavior instructions"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,61 +1,54 @@
|
||||
{
|
||||
"config": {
|
||||
"option": {
|
||||
"api_key": {
|
||||
"name": "API ключ",
|
||||
"description": "Ваш API ключ OpenAI"
|
||||
},
|
||||
"model": {
|
||||
"name": "Модель",
|
||||
"description": "Модель AI для генерации ответов"
|
||||
},
|
||||
"temperature": {
|
||||
"name": "Температура",
|
||||
"description": "Температура для генерации ответов (0-2)"
|
||||
},
|
||||
"max_tokens": {
|
||||
"name": "Максимальное количество токенов",
|
||||
"description": "Максимальное количество токенов в ответе (1-4096)"
|
||||
},
|
||||
"api_endpoint": {
|
||||
"name": "Конечная точка API",
|
||||
"description": "URL конечной точки API"
|
||||
},
|
||||
"request_interval": {
|
||||
"name": "Интервал запросов",
|
||||
"description": "Минимальное время между запросами к API в секундах"
|
||||
"step": {
|
||||
"user": {
|
||||
"title": "Настройка HA Text AI",
|
||||
"description": "Настройка интеграции с OpenAI",
|
||||
"data": {
|
||||
"api_key": "Ваш ключ API OpenAI",
|
||||
"model": "Модель ИИ для генерации ответов",
|
||||
"temperature": "Температура генерации ответов (0-2)",
|
||||
"max_tokens": "Максимальное количество токенов в ответе (1-4096)",
|
||||
"api_endpoint": "URL конечной точки API",
|
||||
"request_interval": "Минимальное время между запросами к API (секунды)"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"options": {
|
||||
"temperature": "Температура",
|
||||
"max_tokens": "Максимальное количество токенов",
|
||||
"request_interval": "Интервал запросов"
|
||||
},
|
||||
"error": {
|
||||
"invalid_api_key": "Неверный API ключ",
|
||||
"cannot_connect": "Не удается подключиться к API",
|
||||
"unknown_error": "Неизвестная ошибка",
|
||||
"invalid_model": "Неверная модель",
|
||||
"rate_limit_exceeded": "Превышен лимит запросов",
|
||||
"context_length_exceeded": "Превышена длина контекста",
|
||||
"api_error": "Ошибка API",
|
||||
"timeout_error": "Время ожидания истекло",
|
||||
"queue_full": "Очередь полна",
|
||||
"invalid_prompt": "Неверный запрос"
|
||||
},
|
||||
"state": {
|
||||
"ready": "Готово",
|
||||
"processing": "Обработка",
|
||||
"error": "Ошибка",
|
||||
"disconnected": "Отключено",
|
||||
"rate_limited": "Ограниченный по скорости",
|
||||
"initializing": "Инициализация"
|
||||
"step": {
|
||||
"init": {
|
||||
"title": "Настройки HA Text AI",
|
||||
"data": {
|
||||
"temperature": "Температура ответов (0-2)",
|
||||
"max_tokens": "Максимальная длина ответа",
|
||||
"request_interval": "Время между запросами"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"services": {
|
||||
"ask_question": "Задать вопрос",
|
||||
"clear_history": "Очистить историю",
|
||||
"get_history": "Получить историю",
|
||||
"set_system_prompt": "Установить системный запрос"
|
||||
"ask_question": {
|
||||
"name": "Задать вопрос",
|
||||
"description": "Отправить вопрос модели ИИ",
|
||||
"fields": {
|
||||
"question": {
|
||||
"name": "Вопрос",
|
||||
"description": "Ваш вопрос для ИИ"
|
||||
}
|
||||
}
|
||||
},
|
||||
"clear_history": {
|
||||
"name": "Очистить историю",
|
||||
"description": "Очистить историю разговора"
|
||||
},
|
||||
"get_history": {
|
||||
"name": "Получить историю",
|
||||
"description": "Получить историю разговора"
|
||||
},
|
||||
"set_system_prompt": {
|
||||
"name": "Установить системный промпт",
|
||||
"description": "Установить инструкции поведения системы"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Binary file not shown.
@@ -4,6 +4,6 @@
|
||||
"domains": ["sensor"],
|
||||
"homeassistant": "2024.11.0",
|
||||
"icon": "mdi:brain",
|
||||
"version": "1.0.7",
|
||||
"version": "1.0.10",
|
||||
"documentation": "https://github.com/smkrv/ha-text-ai"
|
||||
}
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
```
|
||||
ha-text-ai/
|
||||
│
|
||||
├── custom_components/
|
||||
│ └── ha_text_ai/
|
||||
│ ├── __init__.py
|
||||
│ ├── config_flow.py
|
||||
│ ├── coordinator.py
|
||||
│ ├── manifest.json
|
||||
│ ├── sensor.py
|
||||
│ ├── services.yaml
|
||||
│ └── const.py
|
||||
│
|
||||
└── strings/
|
||||
├── en.json
|
||||
└── ru.json
|
||||
```
|
||||
Reference in New Issue
Block a user