diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json new file mode 100644 index 0000000000000000000000000000000000000000..2a245be74fb00f3d1707b8d1175ef517990fb0e4 --- /dev/null +++ b/.devcontainer/devcontainer.json @@ -0,0 +1,32 @@ +{ + "name": "Home Assistant Add-Ons", + "image": "ghcr.io/home-assistant/devcontainer:addons", + "appPort": ["7123:8123", "7357:4357"], + "postStartCommand": "bash devcontainer_bootstrap", + "runArgs": ["-e", "GIT_EDITOR=code --wait", "--privileged"], + "containerEnv": { + "WORKSPACE_DIRECTORY": "${containerWorkspaceFolder}" + }, + "mounts": ["type=volume,target=/var/lib/docker"], + "customizations": { + "vscode": { + "extensions": [ + "timonwong.shellcheck", + "esbenp.prettier-vscode", + "ms-azuretools.vscode-docker" + ], + "settings": { + "terminal.integrated.profiles.linux": { + "zsh": { + "path": "/usr/bin/zsh" + } + }, + "terminal.integrated.defaultProfile.linux": "zsh", + "editor.formatOnPaste": false, + "editor.formatOnSave": true, + "editor.formatOnType": true, + "files.trimTrailingWhitespace": true + } + } + } +} diff --git a/.devcontainer/supervisor_run b/.devcontainer/supervisor_run new file mode 100755 index 0000000000000000000000000000000000000000..3228d35a9211b365c36f657ce1aa84f2ce096214 --- /dev/null +++ b/.devcontainer/supervisor_run @@ -0,0 +1,52 @@ +#!/bin/bash + +set -e + +source /etc/supervisor_scripts/common + +echo "Run Supervisor" + +start_docker +trap "stop_docker" ERR + +function run_supervisor() { + mkdir -p /tmp/supervisor_data + docker run --rm --privileged \ + --name hassio_supervisor \ + --privileged \ + --security-opt seccomp=unconfined \ + --security-opt apparmor=unconfined \ + -v /run/docker.sock:/run/docker.sock:rw \ + -v /run/dbus:/run/dbus:ro \ + -v /run/udev:/run/udev:ro \ + -v /tmp/supervisor_data:/data:rw \ + -v "$WORKSPACE_DIRECTORY/custom_components":/data/homeassistant/custom_components:rw \ + -v /etc/machine-id:/etc/machine-id:ro \ + -e SUPERVISOR_SHARE="/tmp/supervisor_data" \ + -e SUPERVISOR_NAME=hassio_supervisor \ + -e SUPERVISOR_DEV=1 \ + -e SUPERVISOR_MACHINE="qemu${QEMU_ARCH}" \ + "${SUPERVISOR_IMAGE}:${SUPERVISOR_VERSION}" +} + + +if [ "$( docker container inspect -f '{{.State.Status}}' hassio_supervisor )" == "running" ]; then + echo "Restarting Supervisor" + docker rm -f hassio_supervisor + init_dbus + init_udev + init_os_agent + cleanup_lastboot + run_supervisor + stop_docker +else + echo "Starting Supervisor" + docker system prune -f + cleanup_lastboot + cleanup_docker + init_dbus + init_udev + init_os_agent + run_supervisor + stop_docker +fi diff --git a/.vscode/tasks.json b/.vscode/tasks.json new file mode 100644 index 0000000000000000000000000000000000000000..379749e309a4deaa4fb91fe3ebc4c12b318a92d0 --- /dev/null +++ b/.vscode/tasks.json @@ -0,0 +1,44 @@ +{ + // See https://go.microsoft.com/fwlink/?LinkId=733558 + // for the documentation about the tasks.json format + "version": "2.0.0", + "tasks": [ + { + "label": "Start Home Assistant", + "type": "shell", + "command": "/workspaces/hacs/.devcontainer/supervisor_run", + "group": { + "kind": "test", + "isDefault": true + }, + "presentation": { + "reveal": "always", + "panel": "new" + } + }, + { + "label": "Initial Setup", + "type": "shell", + "command": "sudo cp /workspaces/hacs/.devcontainer/intial_setup.tar /tmp/supervisor_data/backup/ && ha backup reload && ha backup restore 5e173c26", + "group": { + "kind": "test", + "isDefault": false + }, + "presentation": { + "reveal": "never" + } + }, + { + "label": "Sync Components", + "type": "shell", + "command": "sudo rsync -avu --delete /workspaces/hacs/custom_components /tmp/supervisor_data/homeassistant", + "group": { + "kind": "test", + "isDefault": false + }, + "presentation": { + "reveal": "never" + } + } + ] +} diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000000000000000000000000000000000000..2ae88cf5b83de6283a6e90fdc82e0c1e778b6fa7 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright © 2023 Erik Hedenström + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/custom_components/openai_conversation_patch/__init__.py b/custom_components/openai_conversation_patch/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..7e84fb96f396544884ccc0608cc3b42bbbc28c67 --- /dev/null +++ b/custom_components/openai_conversation_patch/__init__.py @@ -0,0 +1,76 @@ +from typing import List, Dict, Any, Optional, Tuple +import re +import logging + +import voluptuous as vol +import homeassistant.components.conversation +from homeassistant.components import conversation +from homeassistant.core import Context +from homeassistant.helpers import config_validation as cv +from homeassistant.helpers import intent + +from .const import ( + ATTR_RESPONSE_PARSER_START, + ATTR_RESPONSE_PARSER_END, + ATTR_FIRE_INTENT_NAME, + DEFAULT_PARSER_TOKEN, + DEFAULT_INTENT_NAME, + DOMAIN +) + +_LOGGER = logging.getLogger(__name__) + +CONFIG_SCHEMA = vol.Schema({ + DOMAIN: vol.Schema({ + vol.Required(ATTR_RESPONSE_PARSER_START, default=DEFAULT_PARSER_TOKEN): cv.string, + vol.Required(ATTR_RESPONSE_PARSER_END, default=DEFAULT_PARSER_TOKEN): cv.string, + vol.Required(ATTR_FIRE_INTENT_NAME, default=DEFAULT_INTENT_NAME): cv.slugify + }) +}, extra=vol.ALLOW_EXTRA) + +async def async_setup(hass, config): + """Set up the openai_override component.""" + + from homeassistant.components.openai_conversation import OpenAIAgent + + original = OpenAIAgent.async_process + + async def async_process(self, user_input: conversation.ConversationInput) -> conversation.ConversationResult: + """Handle OpenAI intent.""" + result = await original(self, user_input) + _LOGGER.info("Error code: {}".format(result.response.error_code)) + if result.response.error_code is not None: + return result + + import json + _LOGGER.info(json.dumps(result.response.speech)) + + content = "" + segments = result.response.speech["plain"]["speech"].splitlines() + for segment in segments: + _LOGGER.info("Segment: {}".format(segment)) + if segment.startswith("{"): + service_call = json.loads(segment) + service = service_call.pop("service") + if not service or not service_call: + _LOGGER.info('Missing information') + continue + await hass.services.async_call( + service.split(".")[0], + service.split(".")[1], + service_call, + blocking=True, + limit=0.3) + else: + content = "{}. {}".format(content, segment) + + intent_response = intent.IntentResponse(language=user_input.language) + intent_response.async_set_speech(content) + return conversation.ConversationResult( + response=intent_response, conversation_id=result.conversation_id + ) + + + OpenAIAgent.async_process = async_process + + return True diff --git a/custom_components/openai_conversation_patch/const.py b/custom_components/openai_conversation_patch/const.py new file mode 100644 index 0000000000000000000000000000000000000000..e75bedf34a7ad3ceac9d19cc9b64ead9e3c019de --- /dev/null +++ b/custom_components/openai_conversation_patch/const.py @@ -0,0 +1,8 @@ +DOMAIN = "openai_conversation_patch" + +ATTR_RESPONSE_PARSER_START: str = "parser_start" +ATTR_RESPONSE_PARSER_END = "parser_end" +ATTR_FIRE_INTENT_NAME = "fire_intent" + +DEFAULT_PARSER_TOKEN = "```" +DEFAULT_INTENT_NAME = "openai_service_intent" diff --git a/custom_components/openai_conversation_patch/manifest.json b/custom_components/openai_conversation_patch/manifest.json new file mode 100644 index 0000000000000000000000000000000000000000..ca80156ae09f2ec36735bd9c67e3b9dc8909d13b --- /dev/null +++ b/custom_components/openai_conversation_patch/manifest.json @@ -0,0 +1,15 @@ +{ + "domain": "openai_conversation_patch", + "name": "OpenAI Conversation Patch", + "documentation": "https://gitlab.hedenstroem.com/home-assistant/hacs/openai-conversation-patch", + "version": "0.0.1", + "requirements": [ + "voluptuous>=0.13.1" + ], + "dependencies": [ + "conversation" + ], + "codeowners": [ + "@ehedenst" + ] +}