Skip to content

Commit

Permalink
Merge pull request #240 from NeonGeckoCom/dev
Browse files Browse the repository at this point in the history
Neon Core 22.05.1
  • Loading branch information
NeonDaniel authored Jun 8, 2022
2 parents 0751c07 + abd06d3 commit 99f1c39
Show file tree
Hide file tree
Showing 23 changed files with 317 additions and 47 deletions.
11 changes: 10 additions & 1 deletion .github/workflows/unit_tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -127,4 +127,13 @@ jobs:
uses: actions/upload-artifact@v2
with:
name: skills-module-test-results
path: tests/skills-module-test-results.xml
path: tests/skills-module-test-results.xml

- name: Test Resource Resolution
run: |
pytest test/test_res.py --doctest-modules --junitxml=tests/resource-resolution-test-results.xml
- name: Upload Resource Resolution test results
uses: actions/upload-artifact@v2
with:
name: resource-resolution-test-results
path: tests/resource-resolution-test-results.xml
42 changes: 42 additions & 0 deletions .github/workflows/update_skills_image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
name: Update Skills Image
on:
workflow_dispatch:

env:
REGISTRY: ghcr.io
IMAGE_NAME: ${{ github.repository_owner }}/neon_skills

jobs:
update_default_skills_image:
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
steps:
- name: Checkout repository
uses: actions/checkout@v2
with:
ref: ${{ github.ref }}

- name: Log in to the Container registry
uses: docker/login-action@f054a8b539a109f9f41c372932f1ae047eff08c9
with:
registry: ${{ env.REGISTRY }}
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}

- name: Extract metadata for default_skills Docker
id: default_skills_meta
uses: docker/metadata-action@v2
with:
images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}-default_skills
tags: |
type=ref,event=branch
- name: Build and push default_skills Docker image
uses: docker/build-push-action@ad44023a93711e3deb337508980b4b5e9bcdc5dc
with:
context: .
push: true
tags: ${{ steps.default_skills_meta.outputs.tags }}
labels: ${{ steps.default_skills_meta.outputs.labels }}
target: default_skills
3 changes: 2 additions & 1 deletion docker/.env
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
NEON_SKILLS_DIR=./
NEON_SKILLS_DIR=./
NEON_CONFIG_PATH=./
2 changes: 1 addition & 1 deletion docker/docker-compose.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ volumes:
driver_opts:
type: config
o: bind
device: ./
device: ${NEON_CONFIG_PATH}
services:
neon-messagebus:
container_name: neon-messagebus
Expand Down
2 changes: 1 addition & 1 deletion docker/ngi_local_conf.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,7 +121,7 @@ session:
ttl: 180
tts:
module: coqui
package_spec: neon-tts-plugin-coqui~=0.1
package_spec: neon-tts-plugin-coqui~=0.2
mozilla: {request_url: http://0.0.0.0:5002/api/tts?}
mozilla_remote: {api_url: https://mtts.2022.us/api/tts}
mimic: {voice: ap}
Expand Down
5 changes: 3 additions & 2 deletions neon_core/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,15 +28,16 @@

import sys

from neon_core.config import init_config, get_core_version
from neon_core.config import init_config, get_core_version, \
setup_resolve_resource_file
from os.path import dirname


NEON_ROOT_PATH = dirname(__file__)
sys.path.append(NEON_ROOT_PATH)
init_config()
CORE_VERSION_STR = get_core_version()

setup_resolve_resource_file()

from neon_core.skills import NeonSkill, NeonFallbackSkill
from neon_core.skills.intent_service import NeonIntentService
Expand Down
18 changes: 18 additions & 0 deletions neon_core/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -138,3 +138,21 @@ def get_core_version() -> str:
"(NeonGecko)"
mycroft.version.CORE_VERSION_STR = core_version_str
return core_version_str


def setup_resolve_resource_file():
"""
Override default resolve_resource_file to include resources in neon-core.
Priority: neon-utils, neon-core, ~/.local/share/neon, ~/.neon, mycroft-core
"""
from neon_utils.file_utils import resolve_neon_resource_file
from mycroft.util.file_utils import resolve_resource_file

def patched_resolve_resource_file(res_name):
resource = resolve_neon_resource_file(res_name) or \
resolve_resource_file(res_name)
return resource

import mycroft.util
mycroft.util.file_utils.resolve_resource_file = \
patched_resolve_resource_file
Original file line number Diff line number Diff line change
Expand Up @@ -51,9 +51,11 @@ def parse(self, utterances, lang=None):
else:
LOG.debug(f"Detected language: {detected_lang}")
if detected_lang != self.language_config["internal"].split("-")[0]:
utterances[idx] = self.translator.translate(original,
self.language_config["internal"], lang.split('-', 1)[0]
or detected_lang)
utterances[idx] = self.translator.translate(
original,
self.language_config["internal"],
lang.split('-', 1)[0] or detected_lang)
LOG.info(f"Translated utterance to: {utterances[idx]}")
# add language metadata to context
metadata += [{
"source_lang": lang or self.language_config['internal'],
Expand Down
Binary file added neon_core/res/snd/beep.wav
Binary file not shown.
Binary file added neon_core/res/snd/loaded.wav
Binary file not shown.
4 changes: 4 additions & 0 deletions neon_core/res/text/en-uk/neon.voc
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
neon
leon
nyan
me on
1 change: 1 addition & 0 deletions neon_core/res/text/ru-ru/neon.voc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
неон
1 change: 1 addition & 0 deletions neon_core/res/text/ua-uk/neon.voc
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
неон
35 changes: 30 additions & 5 deletions neon_core/skills/intent_service.py
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
import time
import wave

from mycroft.skills.intent_services import ConverseService

from neon_core.configuration import Configuration
from neon_core.language import get_lang_config
from neon_core.processing_modules.text import TextParsersService
Expand All @@ -37,6 +39,7 @@
from neon_utils.message_utils import get_message_user
from neon_utils.metrics_utils import Stopwatch
from neon_utils.log_utils import LOG
from neon_utils.user_utils import apply_local_user_profile_updates
from neon_utils.configuration_utils import get_neon_device_type,\
get_neon_user_config
from ovos_utils.json_helper import merge_dict
Expand All @@ -60,10 +63,13 @@
class NeonIntentService(IntentService):
def __init__(self, bus):
super().__init__(bus)
self.converse = NeonConverseService(bus)
self.config = Configuration.get().get('context', {})
self.language_config = get_lang_config()

self.default_user = get_neon_user_config()
# Initialize default user to inject into incoming messages
self._default_user = get_neon_user_config()
self._default_user['user']['username'] = "local"

set_default_lang(self.language_config["internal"])

Expand All @@ -79,6 +85,15 @@ def __init__(self, bus):
except Exception as e:
LOG.exception(e)

self.bus.on("neon.profile_update", self.handle_profile_update)

def handle_profile_update(self, message):
updated_profile = message.data.get("profile")
if updated_profile["user"]["username"] == \
self._default_user["user"]["username"]:
apply_local_user_profile_updates(updated_profile,
self._default_user)

def shutdown(self):
self.parser_service.shutdown()

Expand Down Expand Up @@ -146,9 +161,9 @@ def handle_utterance(self, message):

# Ensure user profile data is present
if "user_profiles" not in message.context:
message.context["user_profiles"] = [self.default_user.content]
message.context["user_profiles"] = [self._default_user.content]
message.context["username"] = \
self.default_user.content["user"]["username"]
self._default_user.content["user"]["username"]

# Make sure there is a `transcribed` timestamp
if not message.context["timing"].get("transcribed"):
Expand Down Expand Up @@ -184,11 +199,21 @@ def handle_utterance(self, message):

# TODO: Try the original lang and fallback to translation
# If translated, make sure message.data['lang'] is updated
if message.context.get("translation_data",
[{}])[0].get("was_translated"):
if message.context.get("translation_data") and \
message.context.get("translation_data")[0].get(
"was_translated"):
message.data["lang"] = self.language_config["internal"]
# now pass our modified message to Mycroft
# TODO: Consider how to implement 'and' parsing and converse DM
super().handle_utterance(message)
except Exception as err:
LOG.exception(err)


class NeonConverseService(ConverseService):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)

def _collect_converse_skills(self):
# TODO: Patching bug in ovos-core 0.0.3
return self.get_active_skills()
10 changes: 5 additions & 5 deletions neon_core/util/qml_file_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
from os.path import isdir, join, dirname
from threading import Thread, Event

_HTTP_SERVER = None
_HTTP_SERVER: socketserver.TCPServer = None


class QmlFileHandler(http.server.SimpleHTTPRequestHandler):
Expand All @@ -58,13 +58,13 @@ def start_qml_http_server(skills_dir: str, port: int = 8000):

served_skills_dir = join(qml_dir, "skills")
served_system_dir = join(qml_dir, "system")
if os.path.exists(served_skills_dir):
if os.path.exists(served_skills_dir) or os.path.islink(served_skills_dir):
os.remove(served_skills_dir)
if os.path.exists(served_system_dir):
if os.path.exists(served_system_dir) or os.path.islink(served_skills_dir):
os.remove(served_system_dir)

os.symlink(skills_dir, join(qml_dir, "skills"))
os.symlink(system_dir, join(qml_dir, "system"))
os.symlink(skills_dir, served_skills_dir)
os.symlink(system_dir, served_system_dir)
started_event = Event()
http_daemon = Thread(target=_initialize_http_server,
args=(started_event, qml_dir, port),
Expand Down
74 changes: 66 additions & 8 deletions neon_core/util/skill_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,13 +28,15 @@

import json
import os.path
import re
from copy import copy

import requests

from os import listdir
from os import listdir, makedirs
from tempfile import mkdtemp
from shutil import rmtree
from os.path import expanduser, join, isdir
from os.path import expanduser, join, isdir, dirname, isfile

from ovos_skills_manager.requirements import install_system_deps, pip_install
from ovos_skills_manager.skill_entry import SkillEntry
Expand Down Expand Up @@ -70,6 +72,43 @@ def get_neon_skills_data(skill_meta_repository: str = "https://github.com/neonge
return skills_data


def _write_pip_constraints_to_file(output_file: str = None):
"""
Writes out a constraints file for OSM to use to prevent broken dependencies
:param output_file: path to constraints file to write
"""
from neon_utils.packaging_utils import get_package_dependencies

output_file = output_file or '/etc/mycroft/constraints.txt'
if not isdir(dirname(output_file)):
makedirs(dirname(output_file))

with open(output_file, 'w+') as f:
constraints = get_package_dependencies("neon-core")
for c in copy(constraints):
try:
constraint = re.split('[^a-zA-Z0-9_-]', c, 1)[0] or c
constraints.extend(get_package_dependencies(constraint))
except ModuleNotFoundError:
LOG.warning(f"Ignoring uninstalled dependency: {constraint}")
constraints = [f'{c.split("[")[0]}{c.split("]")[1]}' if '[' in c
else c for c in constraints if '@' not in c]
LOG.debug(f"Got package constraints: {constraints}")
f.write('\n'.join(constraints))
LOG.info(f"Wrote core constraints to file: {output_file}")


def set_osm_constraints_file(constraints_file: str):
"""
Sets the DEFAULT_CONSTRAINTS param for OVOS Skills Manager.
:param constraints_file: path to valid constraints file for neon-core
"""
if not constraints_file:
raise ValueError("constraints_file not defined")
import ovos_skills_manager.requirements
ovos_skills_manager.requirements.DEFAULT_CONSTRAINTS = constraints_file


def install_skills_from_list(skills_to_install: list, config: dict = None):
"""
Installs the passed list of skill URLs
Expand All @@ -85,6 +124,13 @@ def install_skills_from_list(skills_to_install: list, config: dict = None):
token_set = True
set_github_token(config["neon_token"])
LOG.info(f"Added token to request headers: {config.get('neon_token')}")
try:
_write_pip_constraints_to_file()
except PermissionError:
from ovos_utils.xdg_utils import xdg_data_home
constraints_file = join(xdg_data_home(), "neon", "constraints.txt")
_write_pip_constraints_to_file(constraints_file)
set_osm_constraints_file(constraints_file)
for url in skills_to_install:
try:
normalized_url = normalize_github_url(url)
Expand All @@ -104,6 +150,10 @@ def install_skills_from_list(skills_to_install: list, config: dict = None):
if not os.path.isdir(os.path.join(skill_dir, entry.uuid)):
LOG.error(f"Failed to install: "
f"{os.path.join(skill_dir, entry.uuid)}")
if entry.download(skill_dir):
LOG.info(f"Downloaded failed skill: {entry.uuid}")
else:
LOG.error(f"Failed to download: {entry.uuid}")
else:
LOG.info(f"Installed {url} to {skill_dir}")
except Exception as e:
Expand Down Expand Up @@ -149,10 +199,16 @@ def _install_skill_dependencies(skill: SkillEntry):
:param skill: Skill to install dependencies for
"""
sys_deps = skill.requirements.get("system")
requirements = skill.requirements.get("python")
requirements = copy(skill.requirements.get("python"))
if sys_deps:
install_system_deps(sys_deps)
if requirements:
invalid = [r for r in requirements if r.startswith("lingua-franca")]
if any(invalid):
for dep in invalid:
LOG.warning(f"{dep} is not valid under this core"
f" and will be ignored")
requirements.remove(dep)
pip_install(requirements)
LOG.info(f"Installed dependencies for {skill.skill_folder}")

Expand All @@ -172,17 +228,19 @@ def install_local_skills(local_skills_dir: str = "/skills") -> list:
raise ValueError(f"{local_skills_dir} is not a valid directory")
installed_skills = list()
for skill in listdir(local_skills_dir):
if not isdir(skill):
pass
skill_dir = join(local_skills_dir, skill)
if not isdir(skill_dir):
continue
if not isfile(join(skill_dir, "__init__.py")):
continue
LOG.debug(f"Attempting installation of {skill}")
try:
entry = SkillEntry.from_directory(join(local_skills_dir, skill),
github_token)
entry = SkillEntry.from_directory(skill_dir, github_token)
_install_skill_dependencies(entry)
installed_skills.append(skill)
except Exception as e:
LOG.error(f"Exception while installing {skill}")
LOG.error(e)
LOG.exception(e)
if local_skills_dir not in \
get_neon_skills_config().get("extra_directories", []):
LOG.error(f"{local_skills_dir} not found in configuration")
Expand Down
Loading

0 comments on commit 99f1c39

Please sign in to comment.