mirror of
https://github.com/smkrv/ha-text-ai.git
synced 2026-07-22 23:24:03 +08:00
Compare commits
59
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
d3ef31f551 | ||
|
|
ff0c0369e8 | ||
|
|
b0dafe081b | ||
|
|
a88b5d01c1 | ||
|
|
e7d5e62671 | ||
|
|
5f41b9489d | ||
|
|
958e241e0e | ||
|
|
4b3efc0b6f | ||
|
|
1592fc2371 | ||
|
|
d5a6613428 | ||
|
|
9324473c9e | ||
|
|
456e797cca | ||
|
|
f960b9f9b4 | ||
|
|
8449b73423 | ||
|
|
f19ba9aac9 | ||
|
|
524849f6a9 | ||
|
|
0a64a9abe0 | ||
|
|
adb4127f5a | ||
|
|
f463593180 | ||
|
|
e53f257977 | ||
|
|
87199e856a | ||
|
|
bcf7cfbf76 | ||
|
|
9289e1388f | ||
|
|
84db9b7bb8 | ||
|
|
bd1098a181 | ||
|
|
6fd3db0063 | ||
|
|
bf52217cd5 | ||
|
|
b0cf5b3c61 | ||
|
|
347c1675ca | ||
|
|
961ae2d34f | ||
|
|
14827c3adc | ||
|
|
4a88453abc | ||
|
|
bc33d38f5e | ||
|
|
657a5ede86 | ||
|
|
dd046fe3e8 | ||
|
|
7391dbf5b1 | ||
|
|
a0ccb86bd4 | ||
|
|
c0eb5b165d | ||
|
|
9812babc7e | ||
|
|
c61c4570b0 | ||
|
|
d618feffff | ||
|
|
0fdc3c93d3 | ||
|
|
d6e76f7805 | ||
|
|
b05afe1085 | ||
|
|
2a4911f5f8 | ||
|
|
208074d845 | ||
|
|
616ff2c3fe | ||
|
|
987c939956 | ||
|
|
1615cc744e | ||
|
|
2d793a4b25 | ||
|
|
aeeb4d5504 | ||
|
|
daec801073 | ||
|
|
e4916a7b7c | ||
|
|
a08abd76e3 | ||
|
|
a2be709608 | ||
|
|
69002ef926 | ||
|
|
9ade5a7194 | ||
|
|
9b3f4f605b | ||
|
|
b862968d01 |
@@ -0,0 +1,17 @@
|
|||||||
|
name: Validate
|
||||||
|
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
pull_request:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 0 * * *"
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
validate-hacs:
|
||||||
|
runs-on: "ubuntu-latest"
|
||||||
|
steps:
|
||||||
|
- name: HACS validation
|
||||||
|
uses: "hacs/action@main"
|
||||||
|
with:
|
||||||
|
category: "integration"
|
||||||
@@ -2,22 +2,26 @@
|
|||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
    [](https://opensource.org/licenses/MIT) [](https://github.com/hacs/integration)
|
  [](https://opensource.org/licenses/MIT) [](https://github.com/hacs/integration)   
|
||||||
|
|
||||||
<img src="https://github.com/smkrv/ha-text-ai/blob/3e3ec45b195c92989434fde40ae110027f4ea124/misc/icons/icon.png" alt="HA Text AI" width="140"/>
|
<img src="https://github.com/smkrv/ha-text-ai/blob/524849f6a945ec62c2cf6a6b7ecd9a28b37bf0fa/misc/icons/logo.jpg" alt="HA Text AI" height="160"/>
|
||||||
|
|
||||||
### Advanced AI Integration for Home Assistant with multi-provider support
|
### Advanced AI Integration for Home Assistant with LLM multi-provider support
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<p align="center">
|
<p align="center">
|
||||||
Transform your smart home experience with powerful AI assistance powered by multiple AI providers including OpenAI GPT and Anthropic Claude models. Get intelligent responses, automate complex scenarios, and enhance your home automation with advanced natural language processing.
|
Transform your smart home experience with powerful AI assistance powered by multiple AI providers including OpenAI GPT and Anthropic Claude models. Get intelligent responses, automate complex scenarios, and enhance your home automation with advanced natural language processing.
|
||||||
|
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
---
|
---
|
||||||
> [!NOTE]
|
|
||||||
|
> [!IMPORTANT]
|
||||||
> 🚧 ALPHA VERSION 🚧
|
> 🚧 ALPHA VERSION 🚧
|
||||||
> Expect: potential bugs, frequent changes, incomplete features.
|
> Expect: potential bugs, frequent changes, incomplete features.
|
||||||
> 🤝 Community Driven
|
> 🤝 Community Driven
|
||||||
|
>
|
||||||
|
> <a href="https://community.home-assistant.io/t/ha-text-ai-transforming-home-automation-with-multi-provider-language-models/799741"><img src="https://img.shields.io/badge/Community-blue?style=for-the-badge&logo=homeassistant&logoColor=white&color=03a9f4"/></a>
|
||||||
|
|
||||||
## 🌟 Features
|
## 🌟 Features
|
||||||
|
|
||||||
@@ -71,7 +75,7 @@ Transform your smart home experience with powerful AI assistance powered by mult
|
|||||||
|
|
||||||
## 📋 Prerequisites
|
## 📋 Prerequisites
|
||||||
|
|
||||||
- Home Assistant 2023.11 or later
|
- Home Assistant 2024.11 or later
|
||||||
- Active API key from:
|
- Active API key from:
|
||||||
- OpenAI ([Get key](https://platform.openai.com/account/api-keys))
|
- OpenAI ([Get key](https://platform.openai.com/account/api-keys))
|
||||||
- Anthropic ([Get key](https://console.anthropic.com/))
|
- Anthropic ([Get key](https://console.anthropic.com/))
|
||||||
@@ -139,20 +143,61 @@ To be compatible, a provider should support:
|
|||||||
4. Follow the configuration steps
|
4. Follow the configuration steps
|
||||||
|
|
||||||
### Via YAML
|
### Via YAML
|
||||||
|
|
||||||
|
### Platform Configuration (Global Settings)
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
ha_text_ai:
|
ha_text_ai:
|
||||||
api_provider: openai # or anthropic
|
api_provider: openai # Required
|
||||||
api_key: !secret ai_api_key
|
api_key: !secret ai_api_key # Required
|
||||||
model: gpt-4o-mini
|
model: gpt-4o-mini # Strongly recommended
|
||||||
temperature: 0.7
|
temperature: 0.7 # Optional
|
||||||
max_tokens: 1000
|
max_tokens: 1000 # Optional
|
||||||
request_interval: 1.0
|
request_interval: 1.0 # Optional
|
||||||
api_endpoint: https://api.openai.com/v1 # optional, for custom endpoints
|
api_endpoint: https://api.openai.com/v1 # Required
|
||||||
system_prompt: |
|
system_prompt: | # Optional
|
||||||
You are a home automation expert assistant.
|
You are a home automation expert assistant.
|
||||||
Focus on practical and efficient solutions.
|
Focus on practical and efficient solutions.
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Sensor Configuration
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
sensor:
|
||||||
|
- platform: ha_text_ai
|
||||||
|
name: "My AI Assistant" # Required, unique identifier
|
||||||
|
api_provider: openai # Optional (inherits from platform)
|
||||||
|
model: "gpt-4o-mini" # Optional
|
||||||
|
temperature: 0.7 # Optional
|
||||||
|
max_tokens: 1000 # Optional
|
||||||
|
```
|
||||||
|
|
||||||
|
### 📋 Configuration Parameters
|
||||||
|
|
||||||
|
#### Platform Configuration
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Default | Description |
|
||||||
|
|-----------|------|----------|---------|-------------|
|
||||||
|
| `api_provider` | String | ✅ | - | AI service provider (openai, anthropic) |
|
||||||
|
| `api_key` | String | ✅ | - | Authentication key for AI service |
|
||||||
|
| `model` | String | ⚠️ | Provider default | Strongly recommended: Specific AI model to use. If not specified, the provider's default model will be used |
|
||||||
|
| `temperature` | Float | ❌ | 0.7 | Response creativity level (0.0-2.0) |
|
||||||
|
| `max_tokens` | Integer | ❌ | 1000 | Maximum response length |
|
||||||
|
| `request_interval` | Float | ❌ | 1.0 | Delay between API requests |
|
||||||
|
| `api_endpoint` | URL | ⚠️ | Provider default | Custom API endpoint |
|
||||||
|
| `system_prompt` | String | ❌ | - | Default context for AI interactions |
|
||||||
|
|
||||||
|
#### Sensor Configuration
|
||||||
|
|
||||||
|
| Parameter | Type | Required | Default | Description |
|
||||||
|
|-----------|------|----------|---------|-------------|
|
||||||
|
| `platform` | String | ✅ | - | Must be `ha_text_ai` |
|
||||||
|
| `name` | String | ✅ | - | Unique sensor identifier |
|
||||||
|
| `api_provider` | String | ❌ | Platform setting | Override global provider |
|
||||||
|
| `model` | String | ⚠️ | Platform setting | Recommended: Override global model. If not specified, uses platform or provider default |
|
||||||
|
| `temperature` | Float | ❌ | Platform setting | Override global temperature |
|
||||||
|
| `max_tokens` | Integer | ❌ | Platform setting | Override global max tokens |
|
||||||
|
|
||||||
## 🛠️ Available Services
|
## 🛠️ Available Services
|
||||||
|
|
||||||
### ask_question
|
### ask_question
|
||||||
@@ -192,6 +237,143 @@ data:
|
|||||||
filter_model: "gpt-4o" # optional
|
filter_model: "gpt-4o" # optional
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### 🏷️ 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_'
|
||||||
|
# You define only the part after the underscore
|
||||||
|
sensor.ha_text_ai_YOUR_UNIQUE_SUFFIX
|
||||||
|
|
||||||
|
# Examples:
|
||||||
|
sensor.ha_text_ai_gpt # GPT-based sensor
|
||||||
|
sensor.ha_text_ai_claude # Claude-based sensor
|
||||||
|
sensor.ha_text_ai_gpt # Custom suffix
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Response Retrieval
|
||||||
|
```yaml
|
||||||
|
# Use your specific sensor name
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'response') }}
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Practical Usage
|
||||||
|
```yaml
|
||||||
|
automation:
|
||||||
|
- alias: "AI Response with Custom Sensor"
|
||||||
|
action:
|
||||||
|
- service: ha_text_ai.ask_question
|
||||||
|
data:
|
||||||
|
question: "Home automation advice"
|
||||||
|
- service: notify.mobile
|
||||||
|
data:
|
||||||
|
message: >
|
||||||
|
AI Tip:
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'response') }}
|
||||||
|
```
|
||||||
|
|
||||||
|
### 💡 Naming Rules
|
||||||
|
- Prefix is always `sensor.ha_text_ai_`
|
||||||
|
- Add your unique identifier after the underscore
|
||||||
|
- Use lowercase
|
||||||
|
- No spaces allowed
|
||||||
|
- Keep it descriptive but concise
|
||||||
|
|
||||||
|
### 🔍 HA Text AI Sensor Attributes
|
||||||
|
|
||||||
|
#### Model and Provider Information
|
||||||
|
```yaml
|
||||||
|
# Name of the AI model currently in use (e.g., latest version of GPT)
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Model') }} # gpt-4o
|
||||||
|
|
||||||
|
# Service provider for the AI model (determines API endpoint and authentication)
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Api provider') }} # openai
|
||||||
|
|
||||||
|
# Previous or alternative model configuration
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Last model') }} # gpt-4o
|
||||||
|
```
|
||||||
|
|
||||||
|
#### System Status
|
||||||
|
```yaml
|
||||||
|
# Current operational readiness of the AI service API
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Api status') }} # ready
|
||||||
|
|
||||||
|
# Indicates if a request is currently being processed
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Is processing') }} # false
|
||||||
|
|
||||||
|
# Shows if the API has hit its request rate limit
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Is rate limited') }} # false
|
||||||
|
|
||||||
|
# Status of the specific API endpoint being used
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Endpoint status') }} # ready
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Performance Metrics
|
||||||
|
```yaml
|
||||||
|
# Total number of successfully completed API requests
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Successful requests') }} # 0
|
||||||
|
|
||||||
|
# Number of API requests that encountered errors
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Failed requests') }} # 0
|
||||||
|
|
||||||
|
# Mean time taken to receive a response from the AI service
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Average latency') }} # 0
|
||||||
|
|
||||||
|
# Maximum time taken for a single request-response cycle
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Max latency') }} # 0
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Conversation and Token Usage
|
||||||
|
```yaml
|
||||||
|
# Number of previous interactions stored in conversation context
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'History size') }} # 0
|
||||||
|
|
||||||
|
# Total number of tokens used across all interactions
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Total tokens') }} # 0
|
||||||
|
|
||||||
|
# Tokens used in the input prompts
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Prompt tokens') }} # 0
|
||||||
|
|
||||||
|
# Tokens used in the AI's generated responses
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Completion tokens') }} # 0
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Last Interaction Details
|
||||||
|
```yaml
|
||||||
|
# Most recent complete response generated by the AI service
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Response') }} # Last AI response
|
||||||
|
|
||||||
|
# The most recently processed user query or prompt
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Question') }} # Last asked question
|
||||||
|
|
||||||
|
# Precise moment when the last interaction occurred (useful for tracking and logging)
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Last timestamp') }} # Timestamp
|
||||||
|
```
|
||||||
|
|
||||||
|
#### System Health
|
||||||
|
```yaml
|
||||||
|
# Cumulative count of all errors encountered during AI service interactions
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Total errors') }} # 0
|
||||||
|
|
||||||
|
# Indicates if the AI service is currently undergoing scheduled or emergency maintenance
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Is maintenance') }} # false
|
||||||
|
|
||||||
|
# Total continuous operational time of the AI service (in hours or days)
|
||||||
|
{{ state_attr('sensor.ha_text_ai_gpt', 'Uptime') }} # 547,58
|
||||||
|
```
|
||||||
|
|
||||||
|
### 💡 Pro Tips
|
||||||
|
- Always check attribute existence
|
||||||
|
- Use these attributes for monitoring and automation
|
||||||
|
- Some values might be 0 or empty initially
|
||||||
|
|
||||||
|
|
||||||
## 📘 FAQ
|
## 📘 FAQ
|
||||||
|
|
||||||
**Q: Which AI providers are supported?**
|
**Q: Which AI providers are supported?**
|
||||||
@@ -232,11 +414,27 @@ Contributions welcome! Please read our [Contributing Guide](CONTRIBUTING.md).
|
|||||||
|
|
||||||
MIT License - see [LICENSE](LICENSE) for details.
|
MIT License - see [LICENSE](LICENSE) for details.
|
||||||
|
|
||||||
|
## 💡 Support the Project
|
||||||
|
|
||||||
|
The best support is:
|
||||||
|
- Sharing feedback
|
||||||
|
- Contributing ideas
|
||||||
|
- Recommending to friends
|
||||||
|
- Reporting issues
|
||||||
|
- Star the repository
|
||||||
|
|
||||||
|
If you want to say thanks financially, you can send a small token of appreciation in USDT:
|
||||||
|
|
||||||
|
**USDT Wallet (TRC10/TRC20):**
|
||||||
|
`TXC9zYHYPfWUGi4Sv4R1ctTBGScXXQk5HZ`
|
||||||
|
|
||||||
|
*Open-source is built by community passion!* 🚀
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
<div align="center">
|
<div align="center">
|
||||||
|
|
||||||
Made with ❤️ for the Home Assistant Community
|
Made with ❤️ and Claude 3.5 Sonnet for the Home Assistant Community
|
||||||
|
|
||||||
[Report Bug](https://github.com/smkrv/ha-text-ai/issues) · [Request Feature](https://github.com/smkrv/ha-text-ai/issues)
|
[Report Bug](https://github.com/smkrv/ha-text-ai/issues) · [Request Feature](https://github.com/smkrv/ha-text-ai/issues)
|
||||||
|
|
||||||
|
|||||||
@@ -43,6 +43,8 @@ from .const import (
|
|||||||
SERVICE_CLEAR_HISTORY,
|
SERVICE_CLEAR_HISTORY,
|
||||||
SERVICE_GET_HISTORY,
|
SERVICE_GET_HISTORY,
|
||||||
SERVICE_SET_SYSTEM_PROMPT,
|
SERVICE_SET_SYSTEM_PROMPT,
|
||||||
|
DEFAULT_MAX_HISTORY,
|
||||||
|
CONF_MAX_HISTORY_SIZE,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
@@ -89,7 +91,7 @@ async def async_setup(hass: HomeAssistant, config: Dict[str, Any]) -> bool:
|
|||||||
source = os.path.join(os.path.dirname(__file__), 'icons', 'icon.svg')
|
source = os.path.join(os.path.dirname(__file__), 'icons', 'icon.svg')
|
||||||
dest_dir = os.path.join(hass.config.path('www'), 'icons')
|
dest_dir = os.path.join(hass.config.path('www'), 'icons')
|
||||||
os.makedirs(dest_dir, exist_ok=True)
|
os.makedirs(dest_dir, exist_ok=True)
|
||||||
dest = os.path.join(dest_dir, 'icon.svg')
|
dest = os.path.join(dest_dir, 'icon.png')
|
||||||
if not os.path.exists(dest):
|
if not os.path.exists(dest):
|
||||||
shutil.copyfile(source, dest)
|
shutil.copyfile(source, dest)
|
||||||
except Exception as ex:
|
except Exception as ex:
|
||||||
@@ -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:
|
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Set up HA Text AI from a config entry."""
|
"""Set up HA Text AI from a config entry."""
|
||||||
|
_LOGGER.debug(f"Setting up HA Text AI entry: {entry.data}")
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if CONF_API_PROVIDER not in entry.data:
|
if CONF_API_PROVIDER not in entry.data:
|
||||||
_LOGGER.error("API provider not specified")
|
_LOGGER.error("API provider not specified")
|
||||||
raise ConfigEntryNotReady("API provider is required")
|
raise ConfigEntryNotReady("API provider is required")
|
||||||
|
|
||||||
|
# Get configuration
|
||||||
session = aiohttp_client.async_get_clientsession(hass)
|
session = aiohttp_client.async_get_clientsession(hass)
|
||||||
api_provider = entry.data.get(CONF_API_PROVIDER)
|
api_provider = entry.data.get(CONF_API_PROVIDER)
|
||||||
model = entry.data.get(CONF_MODEL, DEFAULT_MODEL)
|
model = entry.data.get(CONF_MODEL, DEFAULT_MODEL)
|
||||||
@@ -212,6 +217,11 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
).rstrip('/')
|
).rstrip('/')
|
||||||
api_key = entry.data[CONF_API_KEY]
|
api_key = entry.data[CONF_API_KEY]
|
||||||
instance_name = entry.data.get(CONF_NAME, entry.entry_id)
|
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
|
is_anthropic = api_provider == API_PROVIDER_ANTHROPIC
|
||||||
|
|
||||||
headers = {
|
headers = {
|
||||||
@@ -242,39 +252,33 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
|||||||
hass=hass,
|
hass=hass,
|
||||||
client=api_client,
|
client=api_client,
|
||||||
model=model,
|
model=model,
|
||||||
update_interval=entry.data.get(CONF_REQUEST_INTERVAL, DEFAULT_REQUEST_INTERVAL),
|
update_interval=request_interval,
|
||||||
instance_name=instance_name,
|
instance_name=instance_name,
|
||||||
max_tokens=entry.data.get(CONF_MAX_TOKENS, DEFAULT_MAX_TOKENS),
|
max_tokens=max_tokens,
|
||||||
temperature=entry.data.get(CONF_TEMPERATURE, DEFAULT_TEMPERATURE),
|
temperature=temperature,
|
||||||
|
max_history_size=max_history_size,
|
||||||
|
context_messages=context_messages,
|
||||||
is_anthropic=is_anthropic,
|
is_anthropic=is_anthropic,
|
||||||
context_messages=entry.data.get(
|
|
||||||
CONF_CONTEXT_MESSAGES,
|
|
||||||
DEFAULT_CONTEXT_MESSAGES
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
|
|
||||||
coordinator.data = coordinator._initial_state.copy()
|
_LOGGER.debug(f"Created coordinator for {instance_name}")
|
||||||
_LOGGER.debug(f"Initial state set for coordinator {instance_name}")
|
|
||||||
|
|
||||||
await coordinator.async_config_entry_first_refresh()
|
|
||||||
|
|
||||||
|
# Store coordinator
|
||||||
hass.data.setdefault(DOMAIN, {})
|
hass.data.setdefault(DOMAIN, {})
|
||||||
hass.data[DOMAIN][entry.entry_id] = coordinator
|
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)
|
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
|
||||||
|
|
||||||
_LOGGER.info(
|
_LOGGER.debug(f"Setup completed for {instance_name}")
|
||||||
"Successfully set up %s instance '%s' with model %s",
|
|
||||||
api_provider,
|
|
||||||
instance_name,
|
|
||||||
model
|
|
||||||
)
|
|
||||||
|
|
||||||
return True
|
return True
|
||||||
|
|
||||||
except Exception as ex:
|
except Exception as err:
|
||||||
_LOGGER.exception("Setup error: %s", str(ex))
|
_LOGGER.exception(f"Error setting up HA Text AI: {err}")
|
||||||
raise ConfigEntryNotReady(f"Setup error: {str(ex)}") from ex
|
raise
|
||||||
|
|
||||||
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
|
||||||
"""Unload a config entry."""
|
"""Unload a config entry."""
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ from .const import (
|
|||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class APIClient:
|
class APIClient:
|
||||||
"""API Client for OpenAI and Anthropic."""
|
"""API Client for OpenAI and Anthropic."""
|
||||||
|
|
||||||
@@ -59,6 +60,7 @@ class APIClient:
|
|||||||
payload: Dict[str, Any],
|
payload: Dict[str, Any],
|
||||||
) -> Dict[str, Any]:
|
) -> Dict[str, Any]:
|
||||||
"""Make API request with retry logic."""
|
"""Make API request with retry logic."""
|
||||||
|
_LOGGER.debug(f"API Request: URL={url}, Payload={payload}")
|
||||||
for attempt in range(API_RETRY_COUNT):
|
for attempt in range(API_RETRY_COUNT):
|
||||||
try:
|
try:
|
||||||
async with timeout(API_TIMEOUT):
|
async with timeout(API_TIMEOUT):
|
||||||
@@ -66,20 +68,23 @@ class APIClient:
|
|||||||
url,
|
url,
|
||||||
json=payload,
|
json=payload,
|
||||||
headers=self.headers,
|
headers=self.headers,
|
||||||
timeout=self.timeout
|
timeout=self.timeout,
|
||||||
) as response:
|
) as response:
|
||||||
|
_LOGGER.debug(f"Response status: {response.status}")
|
||||||
if response.status != 200:
|
if response.status != 200:
|
||||||
error_data = await response.json()
|
error_data = await response.json()
|
||||||
|
_LOGGER.error(f"API error: {error_data}")
|
||||||
raise HomeAssistantError(f"API error: {error_data}")
|
raise HomeAssistantError(f"API error: {error_data}")
|
||||||
return await response.json()
|
return await response.json()
|
||||||
except asyncio.TimeoutError:
|
except asyncio.TimeoutError:
|
||||||
|
_LOGGER.warning(f"Timeout on attempt {attempt + 1}")
|
||||||
if attempt == API_RETRY_COUNT - 1:
|
if attempt == API_RETRY_COUNT - 1:
|
||||||
raise HomeAssistantError("API request timed out")
|
raise HomeAssistantError("API request timed out")
|
||||||
await asyncio.sleep(1 * (attempt + 1))
|
await asyncio.sleep(1 * (attempt + 1))
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
|
_LOGGER.warning(f"API request failed on attempt {attempt + 1}: {str(e)}")
|
||||||
if attempt == API_RETRY_COUNT - 1:
|
if attempt == API_RETRY_COUNT - 1:
|
||||||
raise
|
raise
|
||||||
_LOGGER.warning("API request failed, retrying: %s", str(e))
|
|
||||||
await asyncio.sleep(1 * (attempt + 1))
|
await asyncio.sleep(1 * (attempt + 1))
|
||||||
|
|
||||||
async def create(
|
async def create(
|
||||||
@@ -125,16 +130,14 @@ class APIClient:
|
|||||||
return {
|
return {
|
||||||
"choices": [
|
"choices": [
|
||||||
{
|
{
|
||||||
"message": {
|
"message": {"content": data["choices"][0]["message"]["content"]},
|
||||||
"content": data["choices"][0]["message"]["content"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"usage": {
|
"usage": {
|
||||||
"prompt_tokens": data["usage"]["prompt_tokens"],
|
"prompt_tokens": data["usage"]["prompt_tokens"],
|
||||||
"completion_tokens": data["usage"]["completion_tokens"],
|
"completion_tokens": data["usage"]["completion_tokens"],
|
||||||
"total_tokens": data["usage"]["total_tokens"]
|
"total_tokens": data["usage"]["total_tokens"],
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
async def _create_anthropic_completion(
|
async def _create_anthropic_completion(
|
||||||
@@ -147,19 +150,24 @@ class APIClient:
|
|||||||
"""Create completion using Anthropic API."""
|
"""Create completion using Anthropic API."""
|
||||||
url = f"{self.endpoint}/v1/messages"
|
url = f"{self.endpoint}/v1/messages"
|
||||||
|
|
||||||
# Convert messages to Anthropic format
|
system_prompt = None
|
||||||
system_prompt = next(
|
filtered_messages = []
|
||||||
(msg["content"] for msg in messages if msg["role"] == "system"),
|
for msg in messages:
|
||||||
None
|
if msg['role'] == 'system':
|
||||||
)
|
if system_prompt is None:
|
||||||
conversation = [msg for msg in messages if msg["role"] != "system"]
|
system_prompt = msg['content']
|
||||||
|
else:
|
||||||
|
system_prompt += f" {msg['content']}"
|
||||||
|
else:
|
||||||
|
filtered_messages.append(msg)
|
||||||
|
|
||||||
payload = {
|
payload = {
|
||||||
"model": model,
|
"model": model,
|
||||||
"messages": conversation,
|
"messages": filtered_messages,
|
||||||
"max_tokens": max_tokens,
|
"max_tokens": max_tokens,
|
||||||
"temperature": temperature,
|
"temperature": temperature,
|
||||||
}
|
}
|
||||||
|
|
||||||
if system_prompt:
|
if system_prompt:
|
||||||
payload["system"] = system_prompt
|
payload["system"] = system_prompt
|
||||||
|
|
||||||
@@ -167,14 +175,26 @@ class APIClient:
|
|||||||
return {
|
return {
|
||||||
"choices": [
|
"choices": [
|
||||||
{
|
{
|
||||||
"message": {
|
"message": {"content": data["content"][0]["text"]},
|
||||||
"content": data["content"][0]["text"]
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
"usage": {
|
"usage": {
|
||||||
"prompt_tokens": data["usage"]["input_tokens"],
|
"prompt_tokens": data["usage"]["input_tokens"],
|
||||||
"completion_tokens": data["usage"]["output_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()
|
||||||
|
|||||||
@@ -34,10 +34,21 @@ from .const import (
|
|||||||
MIN_MAX_TOKENS,
|
MIN_MAX_TOKENS,
|
||||||
MAX_MAX_TOKENS,
|
MAX_MAX_TOKENS,
|
||||||
MIN_REQUEST_INTERVAL,
|
MIN_REQUEST_INTERVAL,
|
||||||
|
DEFAULT_NAME_PREFIX,
|
||||||
|
DEFAULT_MAX_HISTORY,
|
||||||
|
CONF_MAX_HISTORY_SIZE,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_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):
|
class HATextAIConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
|
||||||
"""Handle a config flow for HA text AI."""
|
"""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:
|
async def async_step_provider(self, user_input: Optional[Dict[str, Any]] = None) -> FlowResult:
|
||||||
"""Handle provider configuration step."""
|
"""Handle provider configuration step."""
|
||||||
|
self._errors = {}
|
||||||
|
|
||||||
if user_input is None:
|
if user_input is None:
|
||||||
default_endpoint = (
|
default_endpoint = (
|
||||||
DEFAULT_OPENAI_ENDPOINT if self._provider == API_PROVIDER_OPENAI
|
DEFAULT_OPENAI_ENDPOINT if self._provider == API_PROVIDER_OPENAI
|
||||||
else DEFAULT_ANTHROPIC_ENDPOINT
|
else DEFAULT_ANTHROPIC_ENDPOINT
|
||||||
)
|
)
|
||||||
|
|
||||||
suggested_name = f"HA Text AI {len(self._async_current_entries()) + 1}"
|
|
||||||
|
|
||||||
return self.async_show_form(
|
return self.async_show_form(
|
||||||
step_id="provider",
|
step_id="provider",
|
||||||
data_schema=vol.Schema({
|
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_API_KEY): str,
|
||||||
vol.Required(CONF_MODEL, default=DEFAULT_MODEL): str,
|
vol.Required(CONF_MODEL, default=DEFAULT_MODEL): str,
|
||||||
vol.Required(CONF_API_ENDPOINT, default=default_endpoint): 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.Coerce(int),
|
||||||
vol.Range(min=1, max=20)
|
vol.Range(min=1, max=20)
|
||||||
),
|
),
|
||||||
}),
|
vol.Optional(
|
||||||
errors=self._errors
|
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]
|
input_copy = user_input.copy()
|
||||||
await self._async_validate_name(instance_name)
|
|
||||||
if self._errors:
|
|
||||||
return await self.async_step_provider()
|
|
||||||
|
|
||||||
if not await self._async_validate_api(user_input):
|
try:
|
||||||
return await self.async_step_provider()
|
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():
|
for entry in self._async_current_entries():
|
||||||
if entry.data.get(CONF_NAME) == name:
|
if entry.data.get(CONF_NAME, "") == normalized:
|
||||||
self._errors["name"] = "name_exists"
|
raise ValueError("name_exists")
|
||||||
return False
|
|
||||||
return True
|
normalized = normalized[:50]
|
||||||
|
|
||||||
|
if not normalized:
|
||||||
|
raise ValueError("empty")
|
||||||
|
|
||||||
|
return normalized
|
||||||
|
|
||||||
async def _async_validate_api(self, user_input: Dict[str, Any]) -> bool:
|
async def _async_validate_api(self, user_input: Dict[str, Any]) -> bool:
|
||||||
"""Validate API connection."""
|
"""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:
|
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]
|
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(
|
return self.async_create_entry(
|
||||||
title=instance_name,
|
title=instance_name,
|
||||||
data={
|
data=entry_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)
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@@ -243,5 +328,15 @@ class OptionsFlowHandler(config_entries.OptionsFlow):
|
|||||||
vol.Coerce(int),
|
vol.Coerce(int),
|
||||||
vol.Range(min=1, max=20)
|
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)
|
||||||
|
),
|
||||||
})
|
})
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ from homeassistant.helpers import config_validation as cv
|
|||||||
|
|
||||||
# Domain and platforms
|
# Domain and platforms
|
||||||
DOMAIN: Final = "ha_text_ai"
|
DOMAIN: Final = "ha_text_ai"
|
||||||
PLATFORMS: Final = [Platform.SENSOR]
|
PLATFORMS: list[str] = ["sensor"]
|
||||||
|
|
||||||
# Provider configuration
|
# Provider configuration
|
||||||
CONF_API_PROVIDER: Final = "api_provider"
|
CONF_API_PROVIDER: Final = "api_provider"
|
||||||
@@ -29,7 +29,7 @@ CONF_MAX_TOKENS: Final = "max_tokens"
|
|||||||
CONF_API_ENDPOINT: Final = "api_endpoint"
|
CONF_API_ENDPOINT: Final = "api_endpoint"
|
||||||
CONF_REQUEST_INTERVAL: Final = "request_interval"
|
CONF_REQUEST_INTERVAL: Final = "request_interval"
|
||||||
CONF_INSTANCE: Final = "instance"
|
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_IS_ANTHROPIC: Final = "is_anthropic"
|
||||||
CONF_CONTEXT_MESSAGES: Final = "context_messages"
|
CONF_CONTEXT_MESSAGES: Final = "context_messages"
|
||||||
|
|
||||||
@@ -41,6 +41,7 @@ DEFAULT_REQUEST_INTERVAL: Final = 1.0
|
|||||||
DEFAULT_TIMEOUT: Final = 30
|
DEFAULT_TIMEOUT: Final = 30
|
||||||
DEFAULT_MAX_HISTORY: Final = 50
|
DEFAULT_MAX_HISTORY: Final = 50
|
||||||
DEFAULT_NAME: Final = "HA Text AI"
|
DEFAULT_NAME: Final = "HA Text AI"
|
||||||
|
DEFAULT_NAME_PREFIX = "ha_text_ai"
|
||||||
DEFAULT_CONTEXT_MESSAGES: Final = 5
|
DEFAULT_CONTEXT_MESSAGES: Final = 5
|
||||||
|
|
||||||
# Parameter constraints
|
# Parameter constraints
|
||||||
@@ -164,7 +165,7 @@ SERVICE_SCHEMA_GET_HISTORY = vol.Schema({
|
|||||||
vol.Required(CONF_INSTANCE): cv.string,
|
vol.Required(CONF_INSTANCE): cv.string,
|
||||||
vol.Optional("limit", default=10): vol.All(
|
vol.Optional("limit", default=10): vol.All(
|
||||||
vol.Coerce(int),
|
vol.Coerce(int),
|
||||||
vol.Range(min=1, max=100)
|
vol.Range(min=1, max=100),
|
||||||
),
|
),
|
||||||
vol.Optional("filter_model"): cv.string
|
vol.Optional("filter_model"): cv.string
|
||||||
})
|
})
|
||||||
@@ -189,7 +190,7 @@ CONFIG_SCHEMA = vol.Schema({
|
|||||||
vol.Coerce(float),
|
vol.Coerce(float),
|
||||||
vol.Range(min=MIN_REQUEST_INTERVAL, max=MAX_REQUEST_INTERVAL)
|
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.Coerce(int),
|
||||||
vol.Range(min=1, max=100),
|
vol.Range(min=1, max=100),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
from __future__ import annotations
|
from __future__ import annotations
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import traceback
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
from typing import Any, Dict, List, Optional
|
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.helpers.update_coordinator import DataUpdateCoordinator
|
||||||
from homeassistant.util import dt as dt_util
|
from homeassistant.util import dt as dt_util
|
||||||
from homeassistant.exceptions import HomeAssistantError
|
from homeassistant.exceptions import HomeAssistantError
|
||||||
|
from homeassistant.const import CONF_NAME
|
||||||
|
from .config_flow import normalize_name
|
||||||
|
|
||||||
from .const import (
|
from .const import (
|
||||||
DOMAIN,
|
DOMAIN,
|
||||||
@@ -21,11 +24,16 @@ from .const import (
|
|||||||
DEFAULT_TEMPERATURE,
|
DEFAULT_TEMPERATURE,
|
||||||
DEFAULT_MAX_HISTORY,
|
DEFAULT_MAX_HISTORY,
|
||||||
DEFAULT_CONTEXT_MESSAGES,
|
DEFAULT_CONTEXT_MESSAGES,
|
||||||
|
DEFAULT_NAME_PREFIX,
|
||||||
|
CONF_MAX_HISTORY_SIZE,
|
||||||
)
|
)
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
class HATextAICoordinator(DataUpdateCoordinator):
|
class HATextAICoordinator(DataUpdateCoordinator):
|
||||||
|
"""The HA Text AI coordinator."""
|
||||||
|
|
||||||
def __init__(
|
def __init__(
|
||||||
self,
|
self,
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
@@ -41,6 +49,12 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize coordinator."""
|
"""Initialize coordinator."""
|
||||||
self.instance_name = instance_name
|
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.hass = hass
|
||||||
self.client = client
|
self.client = client
|
||||||
self.model = model
|
self.model = model
|
||||||
@@ -61,7 +75,7 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
"total_errors": 0,
|
"total_errors": 0,
|
||||||
"average_latency": 0,
|
"average_latency": 0,
|
||||||
"max_latency": 0,
|
"max_latency": 0,
|
||||||
"min_latency": float('inf'),
|
"min_latency": float("inf"),
|
||||||
},
|
},
|
||||||
"last_response": {
|
"last_response": {
|
||||||
"timestamp": dt_util.utcnow().isoformat(),
|
"timestamp": dt_util.utcnow().isoformat(),
|
||||||
@@ -69,7 +83,8 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
"response": "",
|
"response": "",
|
||||||
"model": model,
|
"model": model,
|
||||||
"instance": instance_name,
|
"instance": instance_name,
|
||||||
"error": None
|
"normalized_name": self.normalized_name,
|
||||||
|
"error": None,
|
||||||
},
|
},
|
||||||
"is_processing": False,
|
"is_processing": False,
|
||||||
"is_rate_limited": False,
|
"is_rate_limited": False,
|
||||||
@@ -105,13 +120,17 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
self.last_response = self._initial_state["last_response"].copy()
|
self.last_response = self._initial_state["last_response"].copy()
|
||||||
self._start_time = dt_util.utcnow()
|
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]:
|
async def _async_update_data(self) -> Dict[str, Any]:
|
||||||
"""Update data via library."""
|
"""Update data via library."""
|
||||||
try:
|
try:
|
||||||
current_state = self._get_current_state()
|
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 = {
|
data = {
|
||||||
"state": current_state,
|
"state": current_state,
|
||||||
@@ -125,6 +144,7 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
"system_prompt": self._system_prompt,
|
"system_prompt": self._system_prompt,
|
||||||
"history_size": len(self._conversation_history),
|
"history_size": len(self._conversation_history),
|
||||||
"conversation_history": self._conversation_history,
|
"conversation_history": self._conversation_history,
|
||||||
|
"normalized_name": self.normalized_name,
|
||||||
}
|
}
|
||||||
|
|
||||||
# Validate data
|
# Validate data
|
||||||
@@ -141,12 +161,15 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
async def async_update_ha_state(self) -> None:
|
async def async_update_ha_state(self) -> None:
|
||||||
"""Update Home Assistant state."""
|
"""Update Home Assistant state."""
|
||||||
try:
|
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()
|
await self.async_request_refresh()
|
||||||
|
|
||||||
# Force update of all entities
|
# 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():
|
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())
|
self.hass.states.async_set(entity_id, self._get_current_state())
|
||||||
|
|
||||||
except Exception as err:
|
except Exception as err:
|
||||||
@@ -164,15 +187,67 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
return STATE_ERROR
|
return STATE_ERROR
|
||||||
return STATE_READY
|
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:
|
try:
|
||||||
|
# Anthropic specific token counting
|
||||||
if self.is_anthropic and hasattr(self.client, 'count_tokens'):
|
if self.is_anthropic and hasattr(self.client, 'count_tokens'):
|
||||||
return sum(self.client.count_tokens(msg['content']) for msg in messages)
|
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:
|
except Exception as e:
|
||||||
_LOGGER.warning(f"Error calculating context tokens: {e}")
|
# Safe fallback with detailed logging
|
||||||
return 0
|
_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(
|
async def async_ask_question(
|
||||||
self,
|
self,
|
||||||
@@ -183,80 +258,133 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
system_prompt: Optional[str] = None,
|
system_prompt: Optional[str] = None,
|
||||||
context_messages: Optional[int] = None,
|
context_messages: Optional[int] = None,
|
||||||
) -> dict:
|
) -> 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(
|
return await self.async_process_question(
|
||||||
question, model, temperature, max_tokens, system_prompt, context_messages
|
question, model, temperature, max_tokens, system_prompt, context_messages
|
||||||
)
|
)
|
||||||
|
|
||||||
async def async_process_question(
|
async def async_process_question(
|
||||||
self,
|
self,
|
||||||
question: str,
|
question: str,
|
||||||
model: Optional[str] = None,
|
model: Optional[str] = None,
|
||||||
temperature: Optional[float] = None,
|
temperature: Optional[float] = None,
|
||||||
max_tokens: Optional[int] = None,
|
max_tokens: Optional[int] = None,
|
||||||
system_prompt: Optional[str] = None,
|
system_prompt: Optional[str] = None,
|
||||||
context_messages: Optional[int] = None,
|
context_messages: Optional[int] = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
temp_context_messages = context_messages or self.context_messages
|
"""
|
||||||
|
Enhanced question processing with intelligent token management.
|
||||||
|
"""
|
||||||
|
try:
|
||||||
|
self._is_processing = True
|
||||||
|
await self.async_update_ha_state()
|
||||||
|
|
||||||
if not question:
|
temp_context_messages = context_messages or self.context_messages
|
||||||
raise ValueError("Question cannot be empty")
|
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:
|
# Prepare messages with system prompt
|
||||||
self._is_processing = True
|
messages = []
|
||||||
await self.async_update_ha_state()
|
if temp_system_prompt:
|
||||||
|
|
||||||
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:
|
|
||||||
messages.append({"role": "system", "content": temp_system_prompt})
|
messages.append({"role": "system", "content": temp_system_prompt})
|
||||||
|
|
||||||
# Add conversation history
|
# Context history management
|
||||||
context_history = self._conversation_history[-temp_context_messages:]
|
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"]})
|
|
||||||
|
|
||||||
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 = {
|
# Dynamic token allocation
|
||||||
"model": temp_model,
|
available_tokens = max(0, temp_max_tokens - context_tokens)
|
||||||
"temperature": temp_temperature,
|
|
||||||
"max_tokens": temp_max_tokens,
|
|
||||||
"messages": messages,
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
# Intelligent context reduction
|
||||||
end_time = dt_util.utcnow()
|
while context_tokens > temp_max_tokens // 2 and context_history:
|
||||||
latency = (end_time - start_time).total_seconds()
|
context_history.pop(0)
|
||||||
self._update_metrics(latency, response)
|
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
|
# Rebuild messages with trimmed context
|
||||||
self._update_history(question, response)
|
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:
|
# Detailed token logging
|
||||||
self._handle_error(err)
|
_LOGGER.debug(
|
||||||
raise HomeAssistantError(f"Failed to process question: {err}")
|
f"Token Analysis: "
|
||||||
|
f"Context Tokens: {context_tokens}, "
|
||||||
|
f"Max Tokens: {temp_max_tokens}, "
|
||||||
|
f"Available Tokens: {available_tokens}"
|
||||||
|
)
|
||||||
|
|
||||||
finally:
|
# Prepare API call with dynamic token management
|
||||||
self._is_processing = False
|
kwargs = {
|
||||||
await self.async_update_ha_state()
|
"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:
|
async def async_process_message(self, question: str, **kwargs) -> dict:
|
||||||
"""Process message using the AI client."""
|
"""Process message using the AI client."""
|
||||||
@@ -272,7 +400,7 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
"response": response["content"],
|
"response": response["content"],
|
||||||
"model": kwargs.get("model", self.model),
|
"model": kwargs.get("model", self.model),
|
||||||
"instance": self.instance_name,
|
"instance": self.instance_name,
|
||||||
"error": None
|
"error": None,
|
||||||
}
|
}
|
||||||
|
|
||||||
return response
|
return response
|
||||||
@@ -283,20 +411,26 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
|
|
||||||
async def _process_anthropic_message(self, question: str, **kwargs) -> dict:
|
async def _process_anthropic_message(self, question: str, **kwargs) -> dict:
|
||||||
"""Process message using Anthropic API."""
|
"""Process message using Anthropic API."""
|
||||||
response = await self.client.messages.create(
|
try:
|
||||||
model=kwargs["model"],
|
_LOGGER.debug(f"Anthropic API call: model={kwargs['model']}, max_tokens={kwargs['max_tokens']}")
|
||||||
max_tokens=kwargs["max_tokens"],
|
response = await self.client.messages.create(
|
||||||
messages=kwargs["messages"],
|
model=kwargs["model"],
|
||||||
temperature=kwargs["temperature"],
|
max_tokens=kwargs["max_tokens"],
|
||||||
)
|
messages=kwargs["messages"],
|
||||||
return {
|
temperature=kwargs["temperature"],
|
||||||
"content": response.content[0].text,
|
)
|
||||||
"tokens": {
|
_LOGGER.debug(f"Anthropic response: tokens={response.usage}")
|
||||||
"prompt": response.usage.input_tokens,
|
return {
|
||||||
"completion": response.usage.output_tokens,
|
"content": response.content[0].text,
|
||||||
"total": response.usage.input_tokens + response.usage.output_tokens
|
"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:
|
async def _process_openai_message(self, question: str, **kwargs) -> dict:
|
||||||
"""Process message using OpenAI API."""
|
"""Process message using OpenAI API."""
|
||||||
@@ -313,8 +447,8 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
"tokens": {
|
"tokens": {
|
||||||
"prompt": response["usage"]["prompt_tokens"],
|
"prompt": response["usage"]["prompt_tokens"],
|
||||||
"completion": response["usage"]["completion_tokens"],
|
"completion": response["usage"]["completion_tokens"],
|
||||||
"total": response["usage"]["total_tokens"]
|
"total": response["usage"]["total_tokens"],
|
||||||
}
|
},
|
||||||
}
|
}
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
_LOGGER.error(f"Error in OpenAI API call: {str(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:
|
def _update_history(self, question: str, response: dict) -> None:
|
||||||
"""Update conversation history."""
|
"""Update conversation history."""
|
||||||
self._conversation_history.append({
|
self._conversation_history.append(
|
||||||
"timestamp": dt_util.utcnow().isoformat(),
|
{
|
||||||
"question": question,
|
"timestamp": dt_util.utcnow().isoformat(),
|
||||||
"response": response["content"]
|
"question": question,
|
||||||
})
|
"response": response["content"],
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
while len(self._conversation_history) > self.max_history_size:
|
while len(self._conversation_history) > self.max_history_size:
|
||||||
self._conversation_history.pop(0)
|
self._conversation_history.pop(0)
|
||||||
|
|
||||||
def _handle_error(self, error: Exception) -> None:
|
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["total_errors"] += 1
|
||||||
self._performance_metrics["failed_requests"] += 1
|
self._performance_metrics["failed_requests"] += 1
|
||||||
|
|
||||||
self.last_response = {
|
error_details = {
|
||||||
"timestamp": dt_util.utcnow().isoformat(),
|
"timestamp": dt_util.utcnow().isoformat(),
|
||||||
"question": "",
|
|
||||||
"response": "",
|
|
||||||
"model": self.model,
|
"model": self.model,
|
||||||
"instance": self.instance_name,
|
"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:
|
async def async_clear_history(self) -> None:
|
||||||
"""Clear conversation history."""
|
"""Clear conversation history."""
|
||||||
self._conversation_history = []
|
self._conversation_history = []
|
||||||
@@ -375,3 +548,8 @@ class HATextAICoordinator(DataUpdateCoordinator):
|
|||||||
"""Set system prompt."""
|
"""Set system prompt."""
|
||||||
self._system_prompt = prompt
|
self._system_prompt = prompt
|
||||||
await self.async_update_ha_state()
|
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)
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 16 KiB |
File diff suppressed because it is too large
Load Diff
|
Before Width: | Height: | Size: 678 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 45 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 12 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 34 KiB |
@@ -23,6 +23,6 @@
|
|||||||
"single_config_entry": false,
|
"single_config_entry": false,
|
||||||
"ssdp": [],
|
"ssdp": [],
|
||||||
"usb": [],
|
"usb": [],
|
||||||
"version": "2.0.0",
|
"version": "2.0.2-alpha",
|
||||||
"zeroconf": []
|
"zeroconf": []
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -58,25 +58,39 @@ from .const import (
|
|||||||
ENTITY_ICON,
|
ENTITY_ICON,
|
||||||
ENTITY_ICON_ERROR,
|
ENTITY_ICON_ERROR,
|
||||||
ENTITY_ICON_PROCESSING,
|
ENTITY_ICON_PROCESSING,
|
||||||
|
DEFAULT_NAME_PREFIX,
|
||||||
|
CONF_MAX_HISTORY_SIZE,
|
||||||
)
|
)
|
||||||
|
|
||||||
from .coordinator import HATextAICoordinator
|
from .coordinator import HATextAICoordinator
|
||||||
|
|
||||||
_LOGGER = logging.getLogger(__name__)
|
_LOGGER = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
async def async_setup_entry(
|
async def async_setup_entry(
|
||||||
hass: HomeAssistant,
|
hass: HomeAssistant,
|
||||||
entry: ConfigEntry,
|
entry: ConfigEntry,
|
||||||
async_add_entities: AddEntitiesCallback,
|
async_add_entities: AddEntitiesCallback,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Set up the HA Text AI sensor."""
|
"""Set up the HA Text AI sensor."""
|
||||||
coordinator = hass.data[DOMAIN][entry.entry_id]
|
_LOGGER.debug(f"Starting sensor setup for entry: {entry.entry_id}")
|
||||||
instance_name = coordinator.instance_name
|
|
||||||
|
|
||||||
_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)
|
instance_name = coordinator.instance_name
|
||||||
async_add_entities([sensor], True)
|
_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):
|
class HATextAISensor(CoordinatorEntity, SensorEntity):
|
||||||
"""HA Text AI Sensor."""
|
"""HA Text AI Sensor."""
|
||||||
@@ -89,19 +103,30 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
|
|||||||
config_entry: ConfigEntry,
|
config_entry: ConfigEntry,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""Initialize the sensor."""
|
"""Initialize the sensor."""
|
||||||
|
_LOGGER.debug(f"Initializing sensor with config entry: {config_entry.data}")
|
||||||
|
|
||||||
super().__init__(coordinator)
|
super().__init__(coordinator)
|
||||||
|
|
||||||
self._config_entry = config_entry
|
self._config_entry = config_entry
|
||||||
self._instance_name = coordinator.instance_name
|
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._conversation_history = []
|
||||||
self._system_prompt = None
|
self._system_prompt = None
|
||||||
|
|
||||||
self._attr_name = f"HA Text AI {self._instance_name}"
|
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}"
|
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(
|
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,
|
entity_registry_enabled_default=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -118,13 +143,15 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
|
|||||||
|
|
||||||
self._attr_device_info = DeviceInfo(
|
self._attr_device_info = DeviceInfo(
|
||||||
identifiers={(DOMAIN, self._attr_unique_id)},
|
identifiers={(DOMAIN, self._attr_unique_id)},
|
||||||
name=self._attr_name, # Используем имя сенсора
|
name=self._attr_name,
|
||||||
manufacturer="Community",
|
manufacturer="Community",
|
||||||
model=f"{model} ({api_provider} provider)",
|
model=f"{model} ({api_provider} provider)",
|
||||||
sw_version="1.0.0",
|
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
|
@property
|
||||||
def available(self) -> bool:
|
def available(self) -> bool:
|
||||||
@@ -180,11 +207,14 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
|
|||||||
data = self.coordinator.data
|
data = self.coordinator.data
|
||||||
attributes = {
|
attributes = {
|
||||||
ATTR_MODEL: self._config_entry.data.get(CONF_MODEL, "Unknown"),
|
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_API_STATUS: self._current_state,
|
||||||
ATTR_TOTAL_ERRORS: self._error_count,
|
ATTR_TOTAL_ERRORS: self._error_count,
|
||||||
ATTR_LAST_ERROR: self._last_error,
|
ATTR_LAST_ERROR: self._last_error,
|
||||||
"instance_name": self._instance_name,
|
"instance_name": self._instance_name,
|
||||||
|
"normalized_name": self._normalized_name,
|
||||||
ATTR_SYSTEM_PROMPT: data.get("system_prompt"),
|
ATTR_SYSTEM_PROMPT: data.get("system_prompt"),
|
||||||
ATTR_IS_PROCESSING: data.get("is_processing", False),
|
ATTR_IS_PROCESSING: data.get("is_processing", False),
|
||||||
ATTR_IS_RATE_LIMITED: data.get("is_rate_limited", False),
|
ATTR_IS_RATE_LIMITED: data.get("is_rate_limited", False),
|
||||||
@@ -199,32 +229,40 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
|
|||||||
metrics = data.get("metrics", {})
|
metrics = data.get("metrics", {})
|
||||||
if isinstance(metrics, dict):
|
if isinstance(metrics, dict):
|
||||||
self._metrics = metrics
|
self._metrics = metrics
|
||||||
attributes.update({
|
attributes.update(
|
||||||
METRIC_TOTAL_TOKENS: metrics.get("total_tokens", 0),
|
{
|
||||||
METRIC_PROMPT_TOKENS: metrics.get("prompt_tokens", 0),
|
METRIC_TOTAL_TOKENS: metrics.get("total_tokens", 0),
|
||||||
METRIC_COMPLETION_TOKENS: metrics.get("completion_tokens", 0),
|
METRIC_PROMPT_TOKENS: metrics.get("prompt_tokens", 0),
|
||||||
METRIC_SUCCESSFUL_REQUESTS: metrics.get("successful_requests", 0),
|
METRIC_COMPLETION_TOKENS: metrics.get("completion_tokens", 0),
|
||||||
METRIC_FAILED_REQUESTS: metrics.get("failed_requests", 0),
|
METRIC_SUCCESSFUL_REQUESTS: metrics.get(
|
||||||
METRIC_AVERAGE_LATENCY: metrics.get("average_latency", 0),
|
"successful_requests", 0
|
||||||
METRIC_MAX_LATENCY: metrics.get("max_latency", 0),
|
),
|
||||||
METRIC_MIN_LATENCY: metrics.get("min_latency", float("inf")),
|
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
|
# Add last response
|
||||||
last_response = data.get("last_response", {})
|
last_response = data.get("last_response", {})
|
||||||
if isinstance(last_response, dict):
|
if isinstance(last_response, dict):
|
||||||
self._last_response = last_response
|
self._last_response = last_response
|
||||||
attributes.update({
|
attributes.update(
|
||||||
ATTR_RESPONSE: last_response.get("response", ""),
|
{
|
||||||
ATTR_QUESTION: last_response.get("question", ""),
|
ATTR_RESPONSE: last_response.get("response", ""),
|
||||||
"last_model": last_response.get("model", ""),
|
ATTR_QUESTION: last_response.get("question", ""),
|
||||||
"last_timestamp": last_response.get("timestamp", ""),
|
"last_model": last_response.get("model", ""),
|
||||||
"last_error": last_response.get("error"),
|
"last_timestamp": last_response.get("timestamp", ""),
|
||||||
})
|
"last_error": last_response.get("error"),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
# Add performance metrics if available
|
# Add performance metrics if available
|
||||||
if ATTR_PERFORMANCE_METRICS in data:
|
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
|
# Add API version if available
|
||||||
if ATTR_API_VERSION in data:
|
if ATTR_API_VERSION in data:
|
||||||
@@ -288,7 +326,7 @@ class HATextAISensor(CoordinatorEntity, SensorEntity):
|
|||||||
"Error handling update for %s: %s",
|
"Error handling update for %s: %s",
|
||||||
self.entity_id,
|
self.entity_id,
|
||||||
err,
|
err,
|
||||||
exc_info=True
|
exc_info=True,
|
||||||
)
|
)
|
||||||
|
|
||||||
self.async_write_ha_state()
|
self.async_write_ha_state()
|
||||||
|
|||||||
@@ -40,15 +40,14 @@ ask_question:
|
|||||||
min: 1
|
min: 1
|
||||||
max: 20
|
max: 20
|
||||||
step: 1
|
step: 1
|
||||||
mode: box
|
mode: slider
|
||||||
|
|
||||||
model:
|
model:
|
||||||
name: Model
|
name: Model
|
||||||
description: "Select AI model to use (optional, overrides default setting)"
|
description: "Select AI model to use (optional, overrides default setting)"
|
||||||
required: false
|
required: false
|
||||||
selector:
|
selector:
|
||||||
text:
|
text: {}
|
||||||
multiline: false
|
|
||||||
|
|
||||||
temperature:
|
temperature:
|
||||||
name: Temperature
|
name: Temperature
|
||||||
@@ -73,3 +72,98 @@ ask_question:
|
|||||||
max: 4096
|
max: 4096
|
||||||
step: 1
|
step: 1
|
||||||
mode: box
|
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
|
||||||
|
|||||||
@@ -1,261 +1,266 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
"provider": {
|
"provider": {
|
||||||
"title": "KI-Anbieter auswählen",
|
"title": "KI-Anbieter auswählen",
|
||||||
"description": "Wählen Sie den KI-Dienst-Anbieter für diese Instanz",
|
"description": "Wählen Sie den KI-Dienstanbieter für diese Instanz aus.",
|
||||||
"data": {
|
"data": {
|
||||||
"api_provider": "API-Anbieter",
|
"api_provider": "API-Anbieter",
|
||||||
"context_messages": "Anzahl der zu speichernden Kontextnachrichten (1-20)"
|
"context_messages": "Anzahl der zu behaltenden 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"
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"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": {
|
"error": {
|
||||||
"step": {
|
"name_exists": "Eine Instanz mit diesem Namen existiert bereits",
|
||||||
"init": {
|
"invalid_name": "Ungültiger Instanzname",
|
||||||
"title": "Instanzeinstellungen aktualisieren",
|
"invalid_auth": "Authentifizierung fehlgeschlagen - überprüfen Sie Ihren API-Schlüssel",
|
||||||
"description": "Einstellungen für diese KI-Assistenten-Instanz ändern",
|
"invalid_api_key": "Ungültiger API-Schlüssel - überprüfen Sie Ihre Anmeldedaten",
|
||||||
"data": {
|
"cannot_connect": "Verbindung zum API-Dienst fehlgeschlagen",
|
||||||
"model": "KI-Modell",
|
"invalid_model": "Das ausgewählte Modell ist nicht verfügbar",
|
||||||
"temperature": "Antwort-Kreativität (0-2)",
|
"rate_limit": "Ratenlimit überschritten",
|
||||||
"max_tokens": "Maximale Antwortlänge (1-4096)",
|
"context_length": "Kontextlänge überschritten",
|
||||||
"request_interval": "Minimales Anfragen-Intervall (0,1-60 Sekunden)",
|
"rate_limit_exceeded": "API-Ratenlimit überschritten",
|
||||||
"context_messages": "Anzahl der vorherigen Nachrichten, die in den Kontext einbezogen werden sollen (1-20)"
|
"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",
|
||||||
"services": {
|
"empty": "Name darf nicht leer sein",
|
||||||
"ask_question": {
|
"invalid_characters": "Name darf nur Buchstaben, Zahlen, Leerzeichen, Unterstriche und Bindestriche enthalten",
|
||||||
"name": "Frage stellen (HA Text AI)",
|
"name_too_long": "Name darf maximal 50 Zeichen lang sein"
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,260 +1,266 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
"provider": {
|
"provider": {
|
||||||
"title": "Select AI Provider",
|
"title": "Select AI Provider",
|
||||||
"description": "Choose which AI service provider to use for this instance",
|
"description": "Choose which AI service provider to use for this instance.",
|
||||||
"data": {
|
"data": {
|
||||||
"api_provider": "API Provider",
|
"api_provider": "API Provider",
|
||||||
"context_messages": "Number of context messages to retain (1-20)"
|
"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"
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"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": {
|
"error": {
|
||||||
"step": {
|
"name_exists": "An instance with this name already exists",
|
||||||
"init": {
|
"invalid_name": "Invalid instance name",
|
||||||
"title": "Update Instance Settings",
|
"invalid_auth": "Authentication failed - check your API key",
|
||||||
"description": "Modify settings for this AI assistant instance",
|
"invalid_api_key": "Invalid API key - please verify your credentials",
|
||||||
"data": {
|
"cannot_connect": "Failed to connect to API service",
|
||||||
"model": "AI model",
|
"invalid_model": "Selected model is not available",
|
||||||
"temperature": "Response creativity (0-2)",
|
"rate_limit": "Rate limit exceeded",
|
||||||
"max_tokens": "Maximum response length (1-4096)",
|
"context_length": "Context length exceeded",
|
||||||
"request_interval": "Minimum request interval (0.1-60 seconds)",
|
"rate_limit_exceeded": "API rate limit exceeded",
|
||||||
"context_messages": "Number of previous messages to include in context (1-20)"
|
"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",
|
||||||
"services": {
|
"unknown": "Unexpected error occurred",
|
||||||
"ask_question": {
|
"empty": "Name cannot be empty",
|
||||||
"name": "Ask Question (HA Text AI)",
|
"invalid_characters": "Name can only contain letters, numbers, spaces, underscores and hyphens",
|
||||||
"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.",
|
"name_too_long": "Name must be 50 characters or less"
|
||||||
"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"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"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"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,260 +1,266 @@
|
|||||||
{
|
{
|
||||||
"config": {
|
"config": {
|
||||||
"step": {
|
"step": {
|
||||||
"provider": {
|
"provider": {
|
||||||
"title": "Выбор провайдера ИИ",
|
"title": "Выбор поставщика ИИ",
|
||||||
"description": "Выберите сервис ИИ для этого экземпляра",
|
"description": "Выберите поставщика услуг ИИ для этой инстанции.",
|
||||||
"data": {
|
"data": {
|
||||||
"api_provider": "Провайдер API",
|
"api_provider": "Поставщик API",
|
||||||
"context_messages": "Количество сообщений в контексте (1-20)"
|
"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": "Произошла непредвиденная ошибка"
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"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": {
|
"error": {
|
||||||
"step": {
|
"name_exists": "Инстанция с таким именем уже существует",
|
||||||
"init": {
|
"invalid_name": "Некорректное имя инстанции",
|
||||||
"title": "Обновление настроек экземпляра",
|
"invalid_auth": "Аутентификация не удалась - проверьте ваш API-ключ",
|
||||||
"description": "Измените настройки для этого помощника ИИ",
|
"invalid_api_key": "Неверный API-ключ - проверьте ваши учетные данные",
|
||||||
"data": {
|
"cannot_connect": "Не удалось подключиться к службе API",
|
||||||
"model": "Модель ИИ",
|
"invalid_model": "Выбранная модель недоступна",
|
||||||
"temperature": "Креативность ответов (0-2)",
|
"rate_limit": "Превышен лимит запросов",
|
||||||
"max_tokens": "Максимальная длина ответа (1-4096)",
|
"context_length": "Превышена длина контекста",
|
||||||
"request_interval": "Минимальный интервал запросов (0.1-60 секунд)",
|
"rate_limit_exceeded": "Превышен лимит запросов API",
|
||||||
"context_messages": "Количество предыдущих сообщений для включения в контекст (1-20)"
|
"maintenance": "Сервис находится на техническом обслуживании",
|
||||||
}
|
"invalid_response": "Получен неверный ответ API",
|
||||||
}
|
"api_error": "Произошла ошибка службы API",
|
||||||
}
|
"timeout": "Запрос превысил время ожидания",
|
||||||
},
|
"invalid_instance": "Указана неверная инстанция",
|
||||||
"services": {
|
"unknown": "Произошла непредвиденная ошибка",
|
||||||
"ask_question": {
|
"empty": "Имя не может быть пустым",
|
||||||
"name": "Задать вопрос (HA Text AI)",
|
"invalid_characters": "Имя может содержать только буквы, цифры, пробелы, подчеркивания и дефисы",
|
||||||
"description": "Отправьте вопрос модели ИИ и получите подробный ответ. Ответ будет сохранен в истории беседы и может быть извлечен позже.",
|
"name_too_long": "Имя должно быть не более 50 символов"
|
||||||
"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": "Минимальная задержка"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
},
|
||||||
|
"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": "Минимальная задержка"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "HA text AI",
|
"name": "HA text AI",
|
||||||
"render_readme": true,
|
"render_readme": true,
|
||||||
"icon": "mdi:robot",
|
"homeassistant": "2024.11.0"
|
||||||
"domains": ["sensor"],
|
|
||||||
"homeassistant": "2024.11.0",
|
|
||||||
"version": "2.0.0",
|
|
||||||
"documentation": "https://github.com/smkrv/ha-text-ai"
|
|
||||||
}
|
}
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 923 KiB |
+20
-14
@@ -2,18 +2,24 @@
|
|||||||
ha-text-ai/
|
ha-text-ai/
|
||||||
│
|
│
|
||||||
├── custom_components/
|
├── custom_components/
|
||||||
│ └── ha_text_ai/
|
├── ha_text_ai/
|
||||||
│ ├── __init__.py
|
│ ├── __init__.py
|
||||||
│ ├── config_flow.py
|
│ ├── config_flow.py
|
||||||
│ ├── coordinator.py
|
│ ├── coordinator.py
|
||||||
│ ├── manifest.json
|
│ ├── manifest.json
|
||||||
│ ├── sensor.py
|
│ ├── sensor.py
|
||||||
│ ├── services.yaml
|
│ ├── services.yaml
|
||||||
│ ├── const.py
|
│ ├── const.py
|
||||||
│ └── api_client.py
|
│ └── api_client.py
|
||||||
│
|
│
|
||||||
└── strings/
|
├── translations/
|
||||||
├── en.json
|
│ ├── en.json
|
||||||
├── de.json
|
│ ├── de.json
|
||||||
└── ru.json
|
│ └── ru.json
|
||||||
|
│
|
||||||
|
└── icons/
|
||||||
|
├── icon.png
|
||||||
|
├── icon@2x.png
|
||||||
|
├── logo.png
|
||||||
|
└── logo@2x.png
|
||||||
```
|
```
|
||||||
|
|||||||
Reference in New Issue
Block a user