diff --git a/README.md b/README.md index a2fa1cf..02b1c4b 100644 --- a/README.md +++ b/README.md @@ -4,8 +4,9 @@

Wiki GitHub Actions Workflow Status - Docker Image GitHub Release + Docker Image + Docker Image

@@ -26,7 +27,7 @@ Meeseeks is an innovative AI assistant built on a multi-agent large language mod | Completed | In-Progress | Planned | Scoping | | :-------: | :---------: | :-----: | :-----: | -| ✅ | 🚧 | 📅 | 🧐 | +| ✅ | 🚧 | 📅 | 🧐 | @@ -34,22 +35,33 @@ Meeseeks is an innovative AI assistant built on a multi-agent large language mod > [!NOTE] > Visit [**Features - Wiki**](https://github.com/bearlike/Personal-Assistant/wiki/Features) for detailed information on tools and integration capabilities. + + + + + + + + + +
Answer questions and interpret sensor informationControl devices and entities
ScreenshotScreenshot
+ - (✅) [LangFuse](https://github.com/langfuse/langfuse) integrations to accurate log and monitor chains. - (✅) Use natural language to interact with integrations and tools. -- (🚧) Simple REST API interface for 3rd party tools to interface with Meeseeks. +- (✅) Simple REST API interface for 3rd party tools to interface with Meeseeks. - (✅) Handles complex user queries by breaking them into actionable steps, executing these steps, and then summarizing on the results. -- (🚧) Custom [Home Assistant Conversation Integration](https://www.home-assistant.io/integrations/conversation/) to allow voice assistance via [**HA Assist**](https://www.home-assistant.io/voice_control/). +- (✅) Custom [Home Assistant Conversation Integration](https://www.home-assistant.io/integrations/conversation/) to allow voice assistance via [**HA Assist**](https://www.home-assistant.io/voice_control/). - (✅) A chat Interface using `streamlit` that shows the action plan, user types, and response from the LLM. ## Extras 👽 Optional feature that users can choose to install to further optimize their experience. -- (🧐) **`Quality`** Use [CRITIC reflection framework](https://arxiv.org/pdf/2305.11738) to reflect on a response to a task/query using external tools via [`[^]`](https://llamahub.ai/l/agent/llama-index-agent-introspective). -- (📅) **`Privacy`** Integrate with [microsoft/presidio](https://github.com/microsoft/presidio) for customizable PII de-identification. +- (📅) **`Quality`** Use [CRITIC reflection framework](https://arxiv.org/pdf/2305.11738) to reflect on a response to a task/query using external tools via [`[^]`](https://llamahub.ai/l/agent/llama-index-agent-introspective). +- (🚧) **`Privacy`** Integrate with [microsoft/presidio](https://github.com/microsoft/presidio) for customizable PII de-identification. ## Integrations 📦 - (✅) [Home Assistant](https://github.com/home-assistant/core) - (🚧) Google Calendar -- (📅) Google Search, Search recent ArXiv papers and summaries, Yahoo Finance, Yelp +- (🚧) Google Search, Search recent ArXiv papers and summaries, Yahoo Finance, Yelp - (🧐) Android Debugging Shell ## Installating and Running Meeseeks diff --git a/docs/screenshot_ha_assist_1.png b/docs/screenshot_ha_assist_1.png new file mode 100644 index 0000000..c8a01a5 Binary files /dev/null and b/docs/screenshot_ha_assist_1.png differ diff --git a/docs/screenshot_ha_assist_2.png b/docs/screenshot_ha_assist_2.png new file mode 100644 index 0000000..b76a86d Binary files /dev/null and b/docs/screenshot_ha_assist_2.png differ diff --git a/meeseeks-api/README.md b/meeseeks-api/README.md index d9a7453..819032d 100644 --- a/meeseeks-api/README.md +++ b/meeseeks-api/README.md @@ -1,6 +1,13 @@ -# meeseeks-api +# Meeseeks API Server +

+ Wiki + Docker Image + GitHub Release +

- REST API Engine wrapped around the meeseeks-core. - No components are explicitly tested for safety or security. Use with caution in a production environment. +- For more information, such as installation, please check out the [Wiki](https://github.com/bearlike/Personal-Assistant/wiki). + [Link to GitHub Repository](https://github.com/bearlike/Personal-Assistant) diff --git a/meeseeks-chat/README.md b/meeseeks-chat/README.md index 31a2396..8968338 100644 --- a/meeseeks-chat/README.md +++ b/meeseeks-chat/README.md @@ -1,5 +1,18 @@ -# meeseeks-chat +# Meeseeks - Chat Interface +

+ Wiki + Docker Image + GitHub Release +

+ + +

+ Screenshot of Meeseks WebUI +

+ + +- Chat Interface wrapped around the meeseeks-core. Powered by Streamlit. +- For more information, such as installation, please check out the [Wiki](https://github.com/bearlike/Personal-Assistant/wiki). -Chat Interface wrapped around the meeseeks-core. Powered by Streamlit. [Link to GitHub](https://github.com/bearlike/Personal-Assistant) diff --git a/meeseeks_ha_conversation/README.md b/meeseeks_ha_conversation/README.md new file mode 100644 index 0000000..f84fcc8 --- /dev/null +++ b/meeseeks_ha_conversation/README.md @@ -0,0 +1,25 @@ +# Home Assistant Conversation Integration for Meeseeks 🚀 + +

+ Wiki + GitHub Release +

+ + + + + + + + + + + +
Answer questions and interpret sensor informationControl devices and entities
ScreenshotScreenshot
+ +- Home Assistant Conversation Integration for Meeseeks. Can be used with HA Assist ⭐. +- Wrapped around the REST API Engine for Meeseeks. 100% coverage of Meeseeks API. +- No components are explicitly tested for safety or security. Use with caution in a production environment. +- For more information, such as installation, please check out the [Wiki](https://github.com/bearlike/Personal-Assistant/wiki). + +[Link to GitHub Repository](https://github.com/bearlike/Personal-Assistant) diff --git a/meeseeks_ha_conversation/__init__.py b/meeseeks_ha_conversation/__init__.py new file mode 100644 index 0000000..99bfb06 --- /dev/null +++ b/meeseeks_ha_conversation/__init__.py @@ -0,0 +1,168 @@ +"""Custom integration to integrate meeseeks_conversation with Home Assistant. + +For more details about this integration, please refer to +https://github.com/bearlike/personal-Assistant/ +""" +from __future__ import annotations + +from typing import Literal + +from homeassistant.components import conversation +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import MATCH_ALL +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError +from homeassistant.helpers import intent, template +from homeassistant.helpers.aiohttp_client import async_get_clientsession +from homeassistant.util import ulid + +from .api import MeeseeksApiClient +from .const import ( + DOMAIN, LOGGER, + CONF_BASE_URL, + CONF_TIMEOUT, + DEFAULT_TIMEOUT, +) +# User-defined imports +from .coordinator import MeeseeksDataUpdateCoordinator +from .exceptions import ( + ApiClientError, + ApiCommError, + ApiJsonError, + ApiTimeoutError +) +# from .helpers import get_exposed_entities + +async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Set up Meeseeks conversation using UI.""" + # https://developers.home-assistant.io/docs/config_entries_index/#setting-up-an-entry + hass.data.setdefault(DOMAIN, {}) + client = MeeseeksApiClient( + base_url=entry.data[CONF_BASE_URL], + timeout=entry.options.get(CONF_TIMEOUT, DEFAULT_TIMEOUT), + session=async_get_clientsession(hass), + ) + + hass.data[DOMAIN][entry.entry_id] = coordinator = MeeseeksDataUpdateCoordinator( + hass, + client, + ) + # https://developers.home-assistant.io/docs/integration_fetching_data#coordinated-single-api-poll-for-data-for-all-entities + await coordinator.async_config_entry_first_refresh() + + try: + # TODO: Heartbeat check is not implemented but it is still wrapped. + response = await client.async_get_heartbeat() + if not response: + raise ApiClientError("Invalid Meeseeks server") + except ApiClientError as err: + raise ConfigEntryNotReady(err) from err + + entry.async_on_unload(entry.add_update_listener(async_reload_entry)) + + conversation.async_set_agent( + hass, entry, MeeseeksAgent(hass, entry, client)) + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload Meeseeks conversation.""" + conversation.async_unset_agent(hass, entry) + return True + + +async def async_reload_entry(hass: HomeAssistant, entry: ConfigEntry) -> None: + """Reload Meeseeks conversation.""" + await async_unload_entry(hass, entry) + await async_setup_entry(hass, entry) + + +class MeeseeksAgent(conversation.AbstractConversationAgent): + """Meeseeks conversation agent.""" + + def __init__(self, hass: HomeAssistant, entry: ConfigEntry, client: MeeseeksApiClient) -> None: + """Initialize the agent.""" + self.hass = hass + self.entry = entry + self.client = client + self.history: dict[str, dict] = {} + + @property + def supported_languages(self) -> list[str] | Literal["*"]: + """Return a list of supported languages.""" + return MATCH_ALL + + async def async_process( + self, user_input: conversation.ConversationInput + ) -> conversation.ConversationResult: + """Process a sentence.""" + # * If needeed in the future, uncomment the following lines + # raw_system_prompt = self.entry.options.get( + # CONF_PROMPT_SYSTEM, DEFAULT_PROMPT_SYSTEM) + # exposed_entities = get_exposed_entities(self.hass) + # ! Currently, history is not used but still implemented for future use + if user_input.conversation_id in self.history: + conversation_id = user_input.conversation_id + messages = self.history[conversation_id] + else: + conversation_id = ulid.ulid() + system_prompt = "" + messages = { + "system": system_prompt, + "context": None, + } + + messages["prompt"] = user_input.text + + try: + response = await self.query(messages) + except HomeAssistantError as err: + LOGGER.error("Something went wrong: %s", err) + intent_response = intent.IntentResponse( + language=user_input.language) + intent_response.async_set_error( + intent.IntentResponseErrorCode.UNKNOWN, + "Something went wrong, please check the logs for more information.", + ) + return conversation.ConversationResult( + response=intent_response, conversation_id=conversation_id + ) + + messages["context"] = response["context"] + self.history[conversation_id] = messages + + intent_response = intent.IntentResponse(language=user_input.language) + intent_response.async_set_speech(response["response"]) + return conversation.ConversationResult( + response=intent_response, conversation_id=conversation_id + ) + + def _async_generate_prompt(self, raw_prompt: str, exposed_entities) -> str: + """Generate a prompt for the user.""" + return template.Template(raw_prompt, self.hass).async_render( + { + "ha_name": self.hass.config.location_name, + "exposed_entities": exposed_entities, + }, + parse_result=False, + ) + + async def query( + self, + messages + ): + """Process a sentence.""" + # model = self.entry.options.get(CONF_MODEL, DEFAULT_MODEL) + # LOGGER.debug("Prompt for %s: %s", model, messages["prompt"]) + + # TODO: $context, and $system are not used but still implemented for + # future use + # * Generator + result = await self.client.async_generate({ + "context": messages["context"], + "system": messages["system"], + "prompt": messages["prompt"], + }) + response: str = result["task_result"] + LOGGER.debug("Response %s", response) + return result diff --git a/meeseeks_ha_conversation/api.py b/meeseeks_ha_conversation/api.py new file mode 100644 index 0000000..472f55a --- /dev/null +++ b/meeseeks_ha_conversation/api.py @@ -0,0 +1,101 @@ +""" Meeseeks API Client. """ +from __future__ import annotations + +import aiohttp +import async_timeout +import json + +# User-defined imports +from .exceptions import ( + ApiClientError, + ApiCommError, + ApiJsonError, + ApiTimeoutError +) +from .const import LOGGER + + +class MeeseeksApiClient: + """Meeseeks API Client.""" + + def __init__( + self, + base_url: str, + timeout: int, + session: aiohttp.ClientSession, + ) -> None: + """Sample API Client.""" + self._base_url = base_url.rstrip("/") + self._api_key = 'msk-strong-password' + self.timeout = timeout + self._session = session + + async def async_get_heartbeat(self) -> bool: + """Get heartbeat from the API.""" + # TODO: Implement a heartbeat check + return True + + async def async_get_models(self) -> any: + """Get models from the API.""" + # TODO: This is monkey-patched for now + response_data = { + "models": [ + { + "name": "meeseeks", + "modified_at": "2023-11-01T00:00:00.000000000-04:00", + "size": 0, + "digest": None + } + ] + } + return json.dumps(response_data) + + async def async_generate(self, data: dict | None = None,) -> any: + """Generate a completion from the API.""" + url_query = f"{self._base_url}/api/query" + data_custom = { + 'query': str(data["prompt"]).strip(), + } + # Pass headers as None to use the default headers + return await self._meeseeks_api_wrapper( + method="post", + url=url_query, + data=data_custom, + headers=None, + ) + + async def _meeseeks_api_wrapper( + self, + method: str, + url: str, + data: dict | None = None, + headers: dict | None = None, + decode_json: bool = True, + ) -> any: + """Get information from the API.""" + if headers is None: + headers = { + 'accept': 'application/json', + 'X-API-KEY': self._api_key, + 'Content-Type': 'application/json', + } + async with async_timeout.timeout(self.timeout): + response = await self._session.request( + method=method, + url=url, + headers=headers, + json=data, + ) + response.raise_for_status() + + if decode_json: + response_data = await response.json() + if response.status == 404: + raise ApiJsonError(response_data["error"]) + LOGGER.debug(f"Response data: {response_data}") + response_data["response"] = response_data["task_result"] + response_data["context"] = response_data["task_result"] + return response_data + else: + LOGGER.debug("Fallback to text response") + return await response.text() diff --git a/meeseeks_ha_conversation/config_flow.py b/meeseeks_ha_conversation/config_flow.py new file mode 100644 index 0000000..baa06ad --- /dev/null +++ b/meeseeks_ha_conversation/config_flow.py @@ -0,0 +1,198 @@ +"""Adds config flow for Meeseeks.""" +from __future__ import annotations + +import types +from typing import Any +import voluptuous as vol + +from homeassistant import config_entries +from homeassistant.data_entry_flow import FlowResult +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers.aiohttp_client import async_create_clientsession +from homeassistant.helpers.selector import ( + NumberSelector, + NumberSelectorConfig, + TemplateSelector, + SelectSelector, + SelectSelectorConfig, + SelectSelectorMode, + SelectOptionDict +) +# User-defined imports +from .api import MeeseeksApiClient +from .const import ( + DOMAIN, LOGGER, + MENU_OPTIONS, + + CONF_BASE_URL, + CONF_API_KEY, + CONF_TIMEOUT, + CONF_MODEL, + CONF_CTX_SIZE, + CONF_MAX_TOKENS, + CONF_MIROSTAT_MODE, + CONF_MIROSTAT_ETA, + CONF_MIROSTAT_TAU, + CONF_TEMPERATURE, + CONF_REPEAT_PENALTY, + CONF_TOP_K, + CONF_TOP_P, + CONF_PROMPT_SYSTEM, + + DEFAULT_BASE_URL, + DEFAULT_API_KEY, + DEFAULT_TIMEOUT, + DEFAULT_MODEL, + DEFAULT_CTX_SIZE, + DEFAULT_MAX_TOKENS, + DEFAULT_MIROSTAT_MODE, + DEFAULT_MIROSTAT_ETA, + DEFAULT_MIROSTAT_TAU, + DEFAULT_TEMPERATURE, + DEFAULT_REPEAT_PENALTY, + DEFAULT_TOP_K, + DEFAULT_TOP_P, + DEFAULT_PROMPT_SYSTEM +) +from .exceptions import ( + ApiClientError, + ApiCommError, + ApiTimeoutError +) + + +STEP_USER_DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_BASE_URL, default=DEFAULT_BASE_URL): str, + vol.Required(CONF_API_KEY, default=DEFAULT_API_KEY): str, + vol.Required(CONF_TIMEOUT, default=DEFAULT_TIMEOUT): int, + } +) + +DEFAULT_OPTIONS = types.MappingProxyType( + { + CONF_BASE_URL: DEFAULT_BASE_URL, + CONF_API_KEY: DEFAULT_API_KEY, + CONF_TIMEOUT: DEFAULT_TIMEOUT, + CONF_MODEL: DEFAULT_MODEL, + CONF_PROMPT_SYSTEM: DEFAULT_PROMPT_SYSTEM + } +) + + +class MeeseeksConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): + """Handle a config flow for Meeseeks Conversation. Handles UI wizard.""" + + VERSION = 1 + + async def async_step_user( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Handle the initial step.""" + if user_input is None: + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA + ) + + # Search for duplicates with the same CONF_BASE_URL value. + for existing_entry in self._async_current_entries(include_ignore=False): + if existing_entry.data.get(CONF_BASE_URL) == user_input[CONF_BASE_URL]: + return self.async_abort(reason="already_configured") + + errors = {} + try: + self.client = MeeseeksApiClient( + base_url=cv.url_no_path(user_input[CONF_BASE_URL]), + timeout=user_input[CONF_TIMEOUT], + session=async_create_clientsession(self.hass), + ) + response = await self.client.async_get_heartbeat() + if not response: + raise vol.Invalid("Invalid Meeseeks server") + # except vol.Invalid: + # errors["base"] = "invalid_url" + # except ApiTimeoutError: + # errors["base"] = "timeout_connect" + # except ApiCommError: + # errors["base"] = "cannot_connect" + # except ApiClientError as exception: + # LOGGER.exception("Unexpected exception: %s", exception) + # errors["base"] = "unknown" + except Exception as exception: + LOGGER.exception("Unexpected exception: %s", exception) + errors["base"] = "unknown" + else: + return self.async_create_entry( + title=f"Meeseeks - {user_input[CONF_BASE_URL]}", + data={ + CONF_BASE_URL: user_input[CONF_BASE_URL] + }, + options={ + CONF_TIMEOUT: user_input[CONF_TIMEOUT] + } + ) + + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors + ) + + @staticmethod + def async_get_options_flow( + config_entry: config_entries.ConfigEntry, + ) -> config_entries.OptionsFlow: + """Create the options flow.""" + return MeeseeksOptionsFlow(config_entry) + + +class MeeseeksOptionsFlow(config_entries.OptionsFlow): + """Meeseeks config flow options handler.""" + + def __init__(self, config_entry: config_entries.ConfigEntry) -> None: + """Initialize options flow.""" + self.config_entry = config_entry + self.options = dict(config_entry.options) + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage the options.""" + return self.async_show_menu( + step_id="init", + menu_options=MENU_OPTIONS + ) + + async def async_step_all_set( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage the options.""" + return self.async_show_menu( + step_id="init", + menu_options=MENU_OPTIONS + ) + + async def async_step_general_config( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage the options.""" + return self.async_show_menu( + step_id="init", + menu_options=MENU_OPTIONS + ) + + async def async_step_prompt_system( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage the options.""" + return self.async_show_menu( + step_id="init", + menu_options=MENU_OPTIONS + ) + + async def async_step_model_config( + self, user_input: dict[str, Any] | None = None + ) -> FlowResult: + """Manage the options.""" + return self.async_show_menu( + step_id="init", + menu_options=MENU_OPTIONS + ) diff --git a/meeseeks_ha_conversation/const.py b/meeseeks_ha_conversation/const.py new file mode 100644 index 0000000..0d7e456 --- /dev/null +++ b/meeseeks_ha_conversation/const.py @@ -0,0 +1,41 @@ +"""Constants for meeseeks_conversation.""" +from logging import Logger, getLogger + +LOGGER: Logger = getLogger(__package__) + +NAME = "Meeseeks" +DOMAIN = "meeseeks_conversation" + +MENU_OPTIONS = ["all_set"] +# MENU_OPTIONS = ["general_config", "model_config", "prompt_system"] + +CONF_BASE_URL = "base_url" +CONF_API_KEY = "api_key" +CONF_TIMEOUT = "timeout" +CONF_MODEL = "chat_model" +CONF_CTX_SIZE = "ctx_size" +CONF_MAX_TOKENS = "max_tokens" +CONF_MIROSTAT_MODE = "mirostat_mode" +CONF_MIROSTAT_ETA = "mirostat_eta" +CONF_MIROSTAT_TAU = "mirostat_tau" +CONF_TEMPERATURE = "temperature" +CONF_REPEAT_PENALTY = "repeat_penalty" +CONF_TOP_K = "top_k" +CONF_TOP_P = "top_p" +CONF_PROMPT_SYSTEM = "prompt" + +DEFAULT_BASE_URL = "http://meeseeks.server:5123" +DEFAULT_API_KEY = "msk-strong-password" +DEFAULT_TIMEOUT = 60 +DEFAULT_MODEL = "llama2:latest" +DEFAULT_CTX_SIZE = 2048 +DEFAULT_MAX_TOKENS = 128 +DEFAULT_MIROSTAT_MODE = "0" +DEFAULT_MIROSTAT_ETA = 0.1 +DEFAULT_MIROSTAT_TAU = 5.0 +DEFAULT_TEMPERATURE = 0.8 +DEFAULT_REPEAT_PENALTY = 1.1 +DEFAULT_TOP_K = 40 +DEFAULT_TOP_P = 0.9 + +DEFAULT_PROMPT_SYSTEM = "" diff --git a/meeseeks_ha_conversation/coordinator.py b/meeseeks_ha_conversation/coordinator.py new file mode 100644 index 0000000..f4d791a --- /dev/null +++ b/meeseeks_ha_conversation/coordinator.py @@ -0,0 +1,43 @@ +"""DataUpdateCoordinator for meeseeks_conversation.""" +from __future__ import annotations + +from datetime import timedelta + +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.update_coordinator import ( + DataUpdateCoordinator, + UpdateFailed, +) + +from .api import MeeseeksApiClient +from .const import DOMAIN, LOGGER +from .exceptions import ApiClientError + + +# https://developers.home-assistant.io/docs/integration_fetching_data#coordinated-single-api-poll-for-data-for-all-entities +class MeeseeksDataUpdateCoordinator(DataUpdateCoordinator): + """Class to manage fetching data from the API.""" + + config_entry: ConfigEntry + + def __init__( + self, + hass: HomeAssistant, + client: MeeseeksApiClient, + ) -> None: + """Initialize.""" + self.client = client + super().__init__( + hass=hass, + logger=LOGGER, + name=DOMAIN, + update_interval=timedelta(minutes=5), + ) + + async def _async_update_data(self): + """Update data via library.""" + try: + return await self.client.async_get_heartbeat() + except ApiClientError as exception: + raise UpdateFailed(exception) from exception diff --git a/meeseeks_ha_conversation/exceptions.py b/meeseeks_ha_conversation/exceptions.py new file mode 100644 index 0000000..e0c898e --- /dev/null +++ b/meeseeks_ha_conversation/exceptions.py @@ -0,0 +1,14 @@ +"""The exceptions used by Extended OpenAI Conversation.""" +from homeassistant.exceptions import HomeAssistantError + +class ApiClientError(HomeAssistantError): + """Exception to indicate a general API error.""" + +class ApiCommError(ApiClientError): + """Exception to indicate a communication error.""" + +class ApiJsonError(ApiClientError): + """Exception to indicate an error with json response.""" + +class ApiTimeoutError(ApiClientError): + """Exception to indicate a timeout error.""" diff --git a/meeseeks_ha_conversation/helpers.py b/meeseeks_ha_conversation/helpers.py new file mode 100644 index 0000000..ab08498 --- /dev/null +++ b/meeseeks_ha_conversation/helpers.py @@ -0,0 +1,24 @@ +"""Helper functions for Meeseeks.""" + +from homeassistant.components.conversation import DOMAIN as CONVERSATION_DOMAIN +from homeassistant.components.homeassistant.exposed_entities import async_should_expose +from homeassistant.core import HomeAssistant +from homeassistant.helpers import entity_registry + + +def get_exposed_entities(hass: HomeAssistant) -> list[dict]: + """Return exposed entities.""" + hass_entity = entity_registry.async_get(hass) + exposed_entities: list[dict] = [] + + for state in hass.states.async_all(): + if async_should_expose(hass, CONVERSATION_DOMAIN, state.entity_id): + entity = hass_entity.async_get(state.entity_id) + exposed_entities.append({ + "entity_id": state.entity_id, + "name": state.name, + "state": state.state, + "aliases": entity.aliases if entity else [], + }) + + return exposed_entities diff --git a/meeseeks_ha_conversation/manifest.json b/meeseeks_ha_conversation/manifest.json new file mode 100644 index 0000000..e506dab --- /dev/null +++ b/meeseeks_ha_conversation/manifest.json @@ -0,0 +1,16 @@ +{ + "domain": "meeseeks_conversation", + "name": "Meeseeks", + "codeowners": [ + "@bearlike" + ], + "config_flow": true, + "dependencies": [ + "conversation" + ], + "documentation": "https://github.com/bearlike/Personal-Assistant", + "integration_type": "service", + "iot_class": "cloud_polling", + "issue_tracker": "https://github.com/bearlike/Personal-Assistant/issues", + "version": "v1.2.0" +} diff --git a/meeseeks_ha_conversation/strings.json b/meeseeks_ha_conversation/strings.json new file mode 100644 index 0000000..06bc60e --- /dev/null +++ b/meeseeks_ha_conversation/strings.json @@ -0,0 +1,42 @@ +{ + "config": { + "step": { + "user": { + "data": { + "base_url": "Base URL", + "api_key": "API Key", + "timeout": "API Timeout" + } + } + }, + "error": { + "cannot_connect": "Unable to connect", + "invalid_url": "Invalid URL", + "timeout_connect": "Timeout while establishing connection", + "unknown": "Unexpected error, please check logs" + }, + "abort": { + "already_configured": "Service is already exist and configured" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "general_config": "General Settings", + "prompt_system": "System Prompt", + "all_set": "Nothing to configure. You're all set!" + } + }, + "general_config": { + "title": "General Settings", + "data": { + "timeout": "API Timeout" + } + }, + "prompt_system": { + "title": "System Prompt" + } + } + } +} diff --git a/meeseeks_ha_conversation/translations/en.json b/meeseeks_ha_conversation/translations/en.json new file mode 100644 index 0000000..eb37c1e --- /dev/null +++ b/meeseeks_ha_conversation/translations/en.json @@ -0,0 +1,58 @@ +{ + "config": { + "step": { + "user": { + "data": { + "base_url": "Base URL", + "api_key": "API Key", + "timeout": "API Timeout" + } + } + }, + "error": { + "cannot_connect": "Failed to connect", + "invalid_url": "Invalid URL", + "timeout_connect": "Timeout establishing connection", + "unknown": "Unexpected error, check logs" + }, + "abort": { + "already_configured": "Service is already configured" + } + }, + "options": { + "step": { + "init": { + "menu_options": { + "general_config": "General Settings", + "model_config": "Model Configuration", + "prompt_system": "System Prompt", + "all_set": "Nothing to configure. You're all set!" + } + }, + "general_config": { + "title": "General Settings", + "data": { + "timeout": "API Timeout" + } + }, + "model_config": { + "title": "Model Configuration", + "data": { + "chat_model": "Model", + "ctx_size": "Context Size", + "max_tokens": "Maximum Tokens", + "mirostat_mode": "Mirostat Mode", + "mirostat_eta": "Mirostat ETA", + "mirostat_tau": "Mirostat TAU", + "repeat_penalty": "Repeat Penalty", + "temperature": "Temperature", + "top_p": "Top P", + "top_k": "Top K" + } + }, + "prompt_system": { + "title": "System Prompt" + } + } + } +}