From 56942b11de6cc6aaebbd7554678b09d47b6db6cc Mon Sep 17 00:00:00 2001 From: Hunor Laczko Date: Sun, 25 Aug 2024 17:18:49 +0200 Subject: [PATCH] First draft --- README.md | 59 +++- custom_components/groq_cloud_api/__init__.py | 52 +++ .../groq_cloud_api/config_flow.py | 224 ++++++++++++ custom_components/groq_cloud_api/const.py | 19 + .../groq_cloud_api/conversation.py | 332 ++++++++++++++++++ .../groq_cloud_api/manifest.json | 17 + .../groq_cloud_api/translations/en.json | 39 ++ .../groq_cloud_api/translations/hu.json | 39 ++ hacs.json | 5 + 9 files changed, 785 insertions(+), 1 deletion(-) create mode 100644 custom_components/groq_cloud_api/__init__.py create mode 100644 custom_components/groq_cloud_api/config_flow.py create mode 100644 custom_components/groq_cloud_api/const.py create mode 100644 custom_components/groq_cloud_api/conversation.py create mode 100644 custom_components/groq_cloud_api/manifest.json create mode 100644 custom_components/groq_cloud_api/translations/en.json create mode 100644 custom_components/groq_cloud_api/translations/hu.json create mode 100644 hacs.json diff --git a/README.md b/README.md index 157c01e..022dfa9 100644 --- a/README.md +++ b/README.md @@ -1 +1,58 @@ -# ha-groq-cloud-api +# Groq Cloud API integration for Home Assistant 🏠 + +This is a custom integration for using Groq Cloud API in the Assist pipeline, reducing the workload on the Home Assistant server. + +Groq Cloud provides multiple models like Llama 3.1, Gemma2, Groq for free! And no card required! (For now...) + +## Requirements 📖 + +- An GroqCloud account 👤 --> You can create one [here](https://console.groq.com/login) +- An `API Key` 🔑 --> You can generate one [here](https://console.groq.com/keys) + +## How to install ⚙️ + +Before configuring the integration you must first install the `custom_integration`. You can do it through HACS or manually + +### HACS ✨ + +1. **Add** ➕ [this repository](https://my.home-assistant.io/redirect/hacs_repository/?owner=HunorLaczko&repository=ha-groq-api&category=integration) to your HACS repositories: + + - **Click** on this link ⤵️ + + [![Add Repository to HACS](https://my.home-assistant.io/badges/hacs_repository.svg)](https://my.home-assistant.io/redirect/hacs_repository/?owner=HunorLaczko&repository=ha-groq-api&category=integration) + + - Or **copy** this url ⤵️ and paste into your HACS custom repostories + + ```url + https://github.com/HunorLaczko/ha-groq-api + ``` + +2. **Install** 💻 the `Groq Cloud API` integration +3. **Restart** 🔁 Home Assistant + +### Manual Install ⌨️ + +1. **Download** this repository +2. **Copy** everything inside the `custom_components` folder into your Home Assistant's `custom_components` folder. +3. **Restart** Home Assistant + +## Configuration 🔧 + +For the options check out the OpenAI Conversation integration [here](https://www.home-assistant.io/integrations/openai_conversation). + +For the available models check out [Groq Cloud](https://console.groq.com/docs/models). + +### Home Assistant Dashboard 💻 + +- Configure the integration by **clicking here** ⤵️ + + [![Add Repository to HACS](https://my.home-assistant.io/badges/config_flow_start.svg)](https://my.home-assistant.io/redirect/config_flow_start/?domain=groq_cloud_api) + +- Or navigate to your `Devices & services` page and click `+ Add Integration` + + +### Acknowledgements + +The code base is mostly identical [OpenAI Conversation integration](https://github.com/home-assistant/core/blob/dev/homeassistant/components/openai_conversation), just using the official Groq library instead of OpenAI. + +The Readme and HACS setup is based on [Fabio's excellent work](https://github.com/fabio-garavini/ha-groq-whisper-stt-api) with the Groq Whisper integration. Check out his other works as well! diff --git a/custom_components/groq_cloud_api/__init__.py b/custom_components/groq_cloud_api/__init__.py new file mode 100644 index 0000000..fd49828 --- /dev/null +++ b/custom_components/groq_cloud_api/__init__.py @@ -0,0 +1,52 @@ +"""Groq Cloud API integration.""" + +import groq + +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_API_KEY, Platform +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import ConfigEntryNotReady + +from .const import LOGGER + +PLATFORMS = (Platform.CONVERSATION,) + +type GroqConfigEntry = ConfigEntry[groq.AsyncClient] + + +async def async_setup_entry(hass: HomeAssistant, entry: GroqConfigEntry) -> bool: + """Load entry.""" + + LOGGER.info("Setting up %s", entry) + + client = groq.AsyncGroq(api_key=entry.data[CONF_API_KEY]) + try: + await hass.async_add_executor_job(client.with_options(timeout=10.0).models.list) + except groq.AuthenticationError as err: + LOGGER.error("Invalid API key: %s", err) + return False + except groq.GroqError as err: + raise ConfigEntryNotReady(err) from err + + entry.runtime_data = client + + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + + entry.async_on_unload(entry.add_update_listener(async_update_listener)) + + return True + + +async def async_update_listener(hass: HomeAssistant, entry: ConfigEntry): + """Update entry.""" + await hass.config_entries.async_reload(entry.entry_id) + + return True + + +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload a config entry.""" + + LOGGER.info("Unloading %s", entry) + + return await hass.config_entries.async_unload_platforms(entry, PLATFORMS) diff --git a/custom_components/groq_cloud_api/config_flow.py b/custom_components/groq_cloud_api/config_flow.py new file mode 100644 index 0000000..f396e1a --- /dev/null +++ b/custom_components/groq_cloud_api/config_flow.py @@ -0,0 +1,224 @@ +"""Config flow for GroqCloud Whisper integration.""" + +from __future__ import annotations + +from typing import Any +from types import MappingProxyType + +import groq + +import voluptuous as vol + +from homeassistant.core import HomeAssistant +from homeassistant.config_entries import ( + ConfigEntry, + ConfigFlow, + ConfigFlowResult, + OptionsFlow, +) +from homeassistant.const import CONF_API_KEY, CONF_NAME, CONF_LLM_HASS_API +from homeassistant.helpers import llm +import homeassistant.helpers.config_validation as cv +from homeassistant.helpers.selector import ( + NumberSelector, + NumberSelectorConfig, + SelectOptionDict, + SelectSelector, + SelectSelectorConfig, + TemplateSelector, +) + +from . import LOGGER +from .const import ( + CONF_PROMPT, + CONF_TEMPERATURE, + DEFAULT_NAME, + CONF_CHAT_MODEL, + CONF_MAX_TOKENS, + CONF_PROMPT, + CONF_RECOMMENDED, + CONF_TEMPERATURE, + CONF_TOP_P, + DOMAIN, + RECOMMENDED_CHAT_MODEL, + RECOMMENDED_MAX_TOKENS, + RECOMMENDED_TEMPERATURE, + RECOMMENDED_TOP_P, +) + +STEP_USER_DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_NAME, default=DEFAULT_NAME): cv.string, + vol.Required(CONF_API_KEY): cv.string, + } +) + +RECOMMENDED_OPTIONS = { + CONF_RECOMMENDED: True, + CONF_LLM_HASS_API: llm.LLM_API_ASSIST, + CONF_PROMPT: llm.DEFAULT_INSTRUCTIONS_PROMPT, +} + + +async def validate_input(hass: HomeAssistant, data: dict[str, Any]): + """Validate the user input allows us to connect. + + Data has the keys from STEP_USER_DATA_SCHEMA with values provided by the user. + """ + client = groq.AsyncGroq(api_key=data[CONF_API_KEY]) + await hass.async_add_executor_job(client.with_options(timeout=10.0).models.list) + + +class GroqConfigFlow(ConfigFlow, domain=DOMAIN): + """Handle UI config flow.""" + + VERSION = 1 + MINOR_VERSION = 0 + + async def async_step_user( + self, + user_input: dict[str, Any] | None = None, + errors: dict[str, str] | None = None, + ) -> ConfigFlowResult: + """Handle initial step.""" + if user_input is None: + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA + ) + + errors = {} + + try: + await validate_input(user_input) + except groq.APIConnectionError: + errors["base"] = "cannot_connect" + except groq.AuthenticationError: + errors["base"] = "invalid_auth" + except Exception: + LOGGER.exception("Unexpected exception") + errors["base"] = "unknown" + else: + return self.async_create_entry( + title="GroqCloud", + data=user_input, + options=RECOMMENDED_OPTIONS, + ) + + return self.async_show_form( + step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors + ) + + + @staticmethod + def async_get_options_flow( + config_entry: ConfigEntry, + ) -> OptionsFlow: + """Create the options flow.""" + return GroqOptionsFlow(config_entry) + + + +class GroqOptionsFlow(OptionsFlow): + """Groq Cloud config flow options handler.""" + + def __init__(self, config_entry: ConfigEntry) -> None: + """Initialize options flow.""" + self.config_entry = config_entry + self.last_rendered_recommended = config_entry.options.get( + CONF_RECOMMENDED, False + ) + + async def async_step_init( + self, user_input: dict[str, Any] | None = None + ) -> ConfigFlowResult: + """Manage the options.""" + options: dict[str, Any] | MappingProxyType[str, Any] = self.config_entry.options + + if user_input is not None: + if user_input[CONF_RECOMMENDED] == self.last_rendered_recommended: + if user_input[CONF_LLM_HASS_API] == "none": + user_input.pop(CONF_LLM_HASS_API) + return self.async_create_entry(title="", data=user_input) + + # Re-render the options again, now with the recommended options shown/hidden + self.last_rendered_recommended = user_input[CONF_RECOMMENDED] + + options = { + CONF_RECOMMENDED: user_input[CONF_RECOMMENDED], + CONF_PROMPT: user_input[CONF_PROMPT], + CONF_LLM_HASS_API: user_input[CONF_LLM_HASS_API], + } + + schema = groq_config_option_schema(self.hass, options) + return self.async_show_form( + step_id="init", + data_schema=vol.Schema(schema), + ) + + +def groq_config_option_schema( + hass: HomeAssistant, + options: dict[str, Any] | MappingProxyType[str, Any], +) -> dict: + """Return a schema for Groq Cloud completion options.""" + hass_apis: list[SelectOptionDict] = [ + SelectOptionDict( + label="No control", + value="none", + ) + ] + hass_apis.extend( + SelectOptionDict( + label=api.name, + value=api.id, + ) + for api in llm.async_get_apis(hass) + ) + + schema = { + vol.Optional( + CONF_PROMPT, + description={ + "suggested_value": options.get( + CONF_PROMPT, llm.DEFAULT_INSTRUCTIONS_PROMPT + ) + }, + ): TemplateSelector(), + vol.Optional( + CONF_LLM_HASS_API, + description={"suggested_value": options.get(CONF_LLM_HASS_API)}, + default="none", + ): SelectSelector(SelectSelectorConfig(options=hass_apis)), + vol.Required( + CONF_RECOMMENDED, default=options.get(CONF_RECOMMENDED, False) + ): bool, + } + + if options.get(CONF_RECOMMENDED): + return schema + + schema.update( + { + vol.Optional( + CONF_CHAT_MODEL, + description={"suggested_value": options.get(CONF_CHAT_MODEL)}, + default=RECOMMENDED_CHAT_MODEL, + ): str, + vol.Optional( + CONF_MAX_TOKENS, + description={"suggested_value": options.get(CONF_MAX_TOKENS)}, + default=RECOMMENDED_MAX_TOKENS, + ): int, + vol.Optional( + CONF_TOP_P, + description={"suggested_value": options.get(CONF_TOP_P)}, + default=RECOMMENDED_TOP_P, + ): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)), + vol.Optional( + CONF_TEMPERATURE, + description={"suggested_value": options.get(CONF_TEMPERATURE)}, + default=RECOMMENDED_TEMPERATURE, + ): NumberSelector(NumberSelectorConfig(min=0, max=2, step=0.05)), + } + ) + return schema \ No newline at end of file diff --git a/custom_components/groq_cloud_api/const.py b/custom_components/groq_cloud_api/const.py new file mode 100644 index 0000000..1662ace --- /dev/null +++ b/custom_components/groq_cloud_api/const.py @@ -0,0 +1,19 @@ +"""Constants for the Groq Cloud API integration.""" + +import logging + +DOMAIN = "groq_cloud_api" + +LOGGER = logging.getLogger(__name__) + +DEFAULT_NAME = "Groq Cloud API" +CONF_RECOMMENDED = "recommended" +CONF_PROMPT = "prompt" +CONF_CHAT_MODEL = "chat_model" +RECOMMENDED_CHAT_MODEL = "llama3-groq-70b-8192-tool-use-preview" +CONF_MAX_TOKENS = "max_tokens" +RECOMMENDED_MAX_TOKENS = 150 +CONF_TOP_P = "top_p" +RECOMMENDED_TOP_P = 1.0 +CONF_TEMPERATURE = "temperature" +RECOMMENDED_TEMPERATURE = 1.0 \ No newline at end of file diff --git a/custom_components/groq_cloud_api/conversation.py b/custom_components/groq_cloud_api/conversation.py new file mode 100644 index 0000000..aa1b7d0 --- /dev/null +++ b/custom_components/groq_cloud_api/conversation.py @@ -0,0 +1,332 @@ +"""Conversation support for Groq Cloud.""" + +from collections.abc import Callable +import json +from typing import Any, Literal + +import groq +from groq._types import NOT_GIVEN +from groq.types.chat import ( + ChatCompletionAssistantMessageParam, + ChatCompletionMessage, + ChatCompletionMessageParam, + ChatCompletionMessageToolCallParam, + ChatCompletionSystemMessageParam, + ChatCompletionToolMessageParam, + ChatCompletionToolParam, + ChatCompletionUserMessageParam, +) +from groq.types.chat.chat_completion_message_tool_call_param import Function +from groq.types.shared_params import FunctionDefinition +import voluptuous as vol +from voluptuous_openapi import convert + +from homeassistant.components import assist_pipeline, conversation +from homeassistant.components.conversation import trace +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import CONF_LLM_HASS_API, MATCH_ALL +from homeassistant.core import HomeAssistant +from homeassistant.exceptions import HomeAssistantError, TemplateError +from homeassistant.helpers import device_registry as dr, intent, llm, template +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.util import ulid + +from . import GroqConfigEntry +from .const import ( + CONF_CHAT_MODEL, + CONF_MAX_TOKENS, + CONF_PROMPT, + CONF_TEMPERATURE, + CONF_TOP_P, + DOMAIN, + LOGGER, + RECOMMENDED_CHAT_MODEL, + RECOMMENDED_MAX_TOKENS, + RECOMMENDED_TEMPERATURE, + RECOMMENDED_TOP_P, +) + +# Max number of back and forth with the LLM to generate a response +MAX_TOOL_ITERATIONS = 10 + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: GroqConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Set up conversation entities.""" + agent = GroqConversationEntity(config_entry) + async_add_entities([agent]) + + +def _format_tool( + tool: llm.Tool, custom_serializer: Callable[[Any], Any] | None +) -> ChatCompletionToolParam: + """Format tool specification.""" + tool_spec = FunctionDefinition( + name=tool.name, + parameters=convert(tool.parameters, custom_serializer=custom_serializer), + ) + if tool.description: + tool_spec["description"] = tool.description + return ChatCompletionToolParam(type="function", function=tool_spec) + + +class GroqConversationEntity( + conversation.ConversationEntity, conversation.AbstractConversationAgent +): + """Groq conversation agent.""" + + _attr_has_entity_name = True + _attr_name = None + + def __init__(self, entry: GroqConfigEntry) -> None: + """Initialize the agent.""" + self.entry = entry + self.history: dict[str, list[ChatCompletionMessageParam]] = {} + self._attr_unique_id = entry.entry_id + self._attr_device_info = dr.DeviceInfo( + identifiers={(DOMAIN, entry.entry_id)}, + name=entry.title, + manufacturer="Groq", + model="Groq Cloud", + entry_type=dr.DeviceEntryType.SERVICE, + ) + if self.entry.options.get(CONF_LLM_HASS_API): + self._attr_supported_features = ( + conversation.ConversationEntityFeature.CONTROL + ) + + @property + def supported_languages(self) -> list[str] | Literal["*"]: + """Return a list of supported languages.""" + return MATCH_ALL + + async def async_added_to_hass(self) -> None: + """When entity is added to Home Assistant.""" + await super().async_added_to_hass() + assist_pipeline.async_migrate_engine( + self.hass, "conversation", self.entry.entry_id, self.entity_id + ) + conversation.async_set_agent(self.hass, self.entry, self) + self.entry.async_on_unload( + self.entry.add_update_listener(self._async_entry_update_listener) + ) + + async def async_will_remove_from_hass(self) -> None: + """When entity will be removed from Home Assistant.""" + conversation.async_unset_agent(self.hass, self.entry) + await super().async_will_remove_from_hass() + + async def async_process( + self, user_input: conversation.ConversationInput + ) -> conversation.ConversationResult: + """Process a sentence.""" + options = self.entry.options + intent_response = intent.IntentResponse(language=user_input.language) + llm_api: llm.APIInstance | None = None + tools: list[ChatCompletionToolParam] | None = None + user_name: str | None = None + llm_context = llm.LLMContext( + platform=DOMAIN, + context=user_input.context, + user_prompt=user_input.text, + language=user_input.language, + assistant=conversation.DOMAIN, + device_id=user_input.device_id, + ) + + if options.get(CONF_LLM_HASS_API): + try: + llm_api = await llm.async_get_api( + self.hass, + options[CONF_LLM_HASS_API], + llm_context, + ) + except HomeAssistantError as err: + LOGGER.error("Error getting LLM API: %s", err) + intent_response.async_set_error( + intent.IntentResponseErrorCode.UNKNOWN, + f"Error preparing LLM API: {err}", + ) + return conversation.ConversationResult( + response=intent_response, conversation_id=user_input.conversation_id + ) + tools = [ + _format_tool(tool, llm_api.custom_serializer) for tool in llm_api.tools + ] + + if user_input.conversation_id is None: + conversation_id = ulid.ulid_now() + messages = [] + + elif user_input.conversation_id in self.history: + conversation_id = user_input.conversation_id + messages = self.history[conversation_id] + + else: + # Conversation IDs are ULIDs. We generate a new one if not provided. + # If an old OLID is passed in, we will generate a new one to indicate + # a new conversation was started. If the user picks their own, they + # want to track a conversation and we respect it. + try: + ulid.ulid_to_bytes(user_input.conversation_id) + conversation_id = ulid.ulid_now() + except ValueError: + conversation_id = user_input.conversation_id + + messages = [] + + if ( + user_input.context + and user_input.context.user_id + and ( + user := await self.hass.auth.async_get_user(user_input.context.user_id) + ) + ): + user_name = user.name + + try: + prompt_parts = [ + template.Template( + llm.BASE_PROMPT + + options.get(CONF_PROMPT, llm.DEFAULT_INSTRUCTIONS_PROMPT), + self.hass, + ).async_render( + { + "ha_name": self.hass.config.location_name, + "user_name": user_name, + "llm_context": llm_context, + }, + parse_result=False, + ) + ] + + except TemplateError as err: + LOGGER.error("Error rendering prompt: %s", err) + intent_response = intent.IntentResponse(language=user_input.language) + intent_response.async_set_error( + intent.IntentResponseErrorCode.UNKNOWN, + f"Sorry, I had a problem with my template: {err}", + ) + return conversation.ConversationResult( + response=intent_response, conversation_id=conversation_id + ) + + if llm_api: + prompt_parts.append(llm_api.api_prompt) + + prompt = "\n".join(prompt_parts) + + # Create a copy of the variable because we attach it to the trace + messages = [ + ChatCompletionSystemMessageParam(role="system", content=prompt), + *messages[1:], + ChatCompletionUserMessageParam(role="user", content=user_input.text), + ] + + LOGGER.debug("Prompt: %s", messages) + LOGGER.debug("Tools: %s", tools) + trace.async_conversation_trace_append( + trace.ConversationTraceEventType.AGENT_DETAIL, + {"messages": messages, "tools": llm_api.tools if llm_api else None}, + ) + + client = self.entry.runtime_data + + # To prevent infinite loops, we limit the number of iterations + for _iteration in range(MAX_TOOL_ITERATIONS): + try: + result = await client.chat.completions.create( + model=options.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL), + messages=messages, + tools=tools or NOT_GIVEN, + max_tokens=options.get(CONF_MAX_TOKENS, RECOMMENDED_MAX_TOKENS), + top_p=options.get(CONF_TOP_P, RECOMMENDED_TOP_P), + temperature=options.get(CONF_TEMPERATURE, RECOMMENDED_TEMPERATURE), + user=conversation_id, + ) + except groq.GroqError as err: + intent_response = intent.IntentResponse(language=user_input.language) + intent_response.async_set_error( + intent.IntentResponseErrorCode.UNKNOWN, + f"Sorry, I had a problem talking to Groq: {err}", + ) + return conversation.ConversationResult( + response=intent_response, conversation_id=conversation_id + ) + + LOGGER.debug("Response %s", result) + response = result.choices[0].message + + def message_convert( + message: ChatCompletionMessage, + ) -> ChatCompletionMessageParam: + """Convert from class to TypedDict.""" + tool_calls: list[ChatCompletionMessageToolCallParam] = [] + if message.tool_calls: + tool_calls = [ + ChatCompletionMessageToolCallParam( + id=tool_call.id, + function=Function( + arguments=tool_call.function.arguments, + name=tool_call.function.name, + ), + type=tool_call.type, + ) + for tool_call in message.tool_calls + ] + param = ChatCompletionAssistantMessageParam( + role=message.role, + content=message.content, + ) + if tool_calls: + param["tool_calls"] = tool_calls + return param + + messages.append(message_convert(response)) + tool_calls = response.tool_calls + + if not tool_calls or not llm_api: + break + + for tool_call in tool_calls: + tool_input = llm.ToolInput( + tool_name=tool_call.function.name, + tool_args=json.loads(tool_call.function.arguments), + ) + LOGGER.debug( + "Tool call: %s(%s)", tool_input.tool_name, tool_input.tool_args + ) + + try: + tool_response = await llm_api.async_call_tool(tool_input) + except (HomeAssistantError, vol.Invalid) as e: + tool_response = {"error": type(e).__name__} + if str(e): + tool_response["error_text"] = str(e) + + LOGGER.debug("Tool response: %s", tool_response) + messages.append( + ChatCompletionToolMessageParam( + role="tool", + tool_call_id=tool_call.id, + content=json.dumps(tool_response), + ) + ) + + self.history[conversation_id] = messages + + intent_response = intent.IntentResponse(language=user_input.language) + intent_response.async_set_speech(response.content or "") + return conversation.ConversationResult( + response=intent_response, conversation_id=conversation_id + ) + + async def _async_entry_update_listener( + self, hass: HomeAssistant, entry: ConfigEntry + ) -> None: + """Handle options update.""" + # Reload as we update device info + entity name + supported features + await hass.config_entries.async_reload(entry.entry_id) \ No newline at end of file diff --git a/custom_components/groq_cloud_api/manifest.json b/custom_components/groq_cloud_api/manifest.json new file mode 100644 index 0000000..9acfb09 --- /dev/null +++ b/custom_components/groq_cloud_api/manifest.json @@ -0,0 +1,17 @@ +{ + "domain": "groq_cloud_api", + "name": "Groq Cloud API", + "version": "0.0.1", + "codeowners": [ + "@HunorLaczko" + ], + "config_flow": true, + "dependencies": [], + "documentation": "https://github.com/HunorLaczko/ha-groq-cloud-api", + "issue_tracker": "https://github.com/HunorLaczko/ha-groq-cloud-api/issues", + "homekit": {}, + "iot_class": "cloud_push", + "requirements": [], + "ssdp": [], + "zeroconf": [] +} diff --git a/custom_components/groq_cloud_api/translations/en.json b/custom_components/groq_cloud_api/translations/en.json new file mode 100644 index 0000000..842ad1a --- /dev/null +++ b/custom_components/groq_cloud_api/translations/en.json @@ -0,0 +1,39 @@ +{ + "config": { + "step": { + "user": { + "data": { + "api_key": "[%key:common::config_flow::data::api_key%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + }, + "options": { + "step": { + "init": { + "data": { + "prompt": "Instructions", + "chat_model": "[%key:common::generic::model%]", + "max_tokens": "Maximum tokens to return in response", + "temperature": "Temperature", + "top_p": "Top P", + "llm_hass_api": "[%key:common::config_flow::data::llm_hass_api%]", + "recommended": "Recommended model settings" + }, + "data_description": { + "prompt": "Instruct how the LLM should respond. This can be a template." + } + } + } + }, + "exceptions": { + "invalid_config_entry": { + "message": "Invalid config entry provided. Got {config_entry}" + } + } + } \ No newline at end of file diff --git a/custom_components/groq_cloud_api/translations/hu.json b/custom_components/groq_cloud_api/translations/hu.json new file mode 100644 index 0000000..5782c4f --- /dev/null +++ b/custom_components/groq_cloud_api/translations/hu.json @@ -0,0 +1,39 @@ +{ + "config": { + "step": { + "user": { + "data": { + "api_key": "[%key:common::config_flow::data::api_key%]" + } + } + }, + "error": { + "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", + "unknown": "[%key:common::config_flow::error::unknown%]" + } + }, + "options": { + "step": { + "init": { + "data": { + "prompt": "Utasítások", + "chat_model": "[%key:common::generic::model%]", + "max_tokens": "Válaszban visszatérítendő maximális tokenek száma", + "temperature": "Hőmérséklet", + "top_p": "Top P", + "llm_hass_api": "[%key:common::config_flow::data::llm_hass_api%]", + "recommended": "Ajánlott modellbeállítások" + }, + "data_description": { + "prompt": "Add meg, hogy LLM hogyan reagáljon. Ez egy sablon is lehet." + } + } + } + }, + "exceptions": { + "invalid_config_entry": { + "message": "Érvénytelen beállítás megadva. Ezt kaptam: {config_entry}" + } + } + } \ No newline at end of file diff --git a/hacs.json b/hacs.json new file mode 100644 index 0000000..d6854ee --- /dev/null +++ b/hacs.json @@ -0,0 +1,5 @@ +{ + "name": "Groq Cloud API", + "render_readme": true, + "homeassistant": "2024.6.0" + } \ No newline at end of file