diff --git a/.github/workflows/pytest.yml b/.github/workflows/pytest.yml index 9c642da9d..b980817df 100644 --- a/.github/workflows/pytest.yml +++ b/.github/workflows/pytest.yml @@ -1,11 +1,10 @@ # This workflow will install Python dependencies and run tests with a single version of Python # For more information see: https://help.github.com/actions/language-and-framework-guides/using-python-with-github-actions -name: Pytest and Sonarcloud analysis +name: Unittest & Quality on: push: - pull_request: jobs: test: @@ -22,15 +21,15 @@ jobs: with: python-version: 3.7 - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install -r requirements_test.txt + uses: py-actions/py-dependency-install@v2 + with: + path: requirements_test.txt + - name: Install Portaudio + run: sudo apt-get install libportaudio2 - name: Tests - run: | - pytest tests/ --cov=core/ --cov-report=xml + run: pytest tests/ --cov=core/ --cov-report=xml - name: Fix paths - run: | - sed -i 's/\/home\/runner\/work\/ProjectAlice\/ProjectAlice\//\/github\/workspace\//g' coverage.xml + run: sed -i 's/\/home\/runner\/work\/ProjectAlice\/ProjectAlice\//\/github\/workspace\//g' coverage.xml - name: Sonarcloud scan uses: sonarsource/sonarcloud-github-action@master env: diff --git a/.gitprefix b/.gitprefix new file mode 100644 index 000000000..756dfefb8 --- /dev/null +++ b/.gitprefix @@ -0,0 +1,28 @@ +:art: cleanup +:rocket: deploy +:pencil2: typo +:construction: WIP +:heavy_plus_sign: add dependency +:heavy_minus_sign: remove dependency +:speaker: add logs +:mute: remove logs +:bug: fix +:globe_with_meridians: i18n +:poop: crap +:boom: breaking +:beers: drunk coding +:lipstick: cosmetic +:lock: fix security issue +:heavy_exclamation_mark: woot +:white_check_mark: add test +:green_heart: fix ci +:recycle: refactor +:children_crossing: improve user experience +:wastebasket: deprecated +:see_no_evil: gitignore +:alien: api change +:sparkles: new feature +:wheelchair: improve accessibility +:zap: improve performance +:fire: remove code or file +:ambulance: hotfix diff --git a/README.md b/README.md index 3416adfee..b8468e642 100644 --- a/README.md +++ b/README.md @@ -6,6 +6,8 @@
+
+
diff --git a/config-schema.json b/config-schema.json
index 8678c05d6..fe8be1335 100644
--- a/config-schema.json
+++ b/config-schema.json
@@ -44,7 +44,9 @@
"mqtt",
"tts",
"wakeword",
- "advanced debug"
+ "advanced debug",
+ "audio",
+ "scenarios"
]
},
"isSensitive" : {
@@ -66,11 +68,19 @@
},
"beforeUpdate": {
"type" : "string",
- "pattern": "^[a-z].*$"
+ "pattern": "^(?:[A-Z][a-zA-Z]+\\.)?[a-z][a-zA-Z0-9]+$"
},
"onUpdate" : {
"type" : "string",
- "pattern": "^[a-z].*$"
+ "pattern": "^(?:[A-Z][a-zA-Z]+\\.)?[a-z][a-zA-Z0-9]+$"
+ },
+ "onStart" : {
+ "type" : "string",
+ "pattern": "^(?:[A-Z][a-zA-Z]+\\.)?[a-z][a-zA-Z0-9]+$"
+ },
+ "onInit" : {
+ "type" : "string",
+ "pattern": "^(?:[A-Z][a-zA-Z]+\\.)?[a-z][a-zA-Z0-9]+$"
},
"parent" : {
"type" : "object",
diff --git a/configTemplate.json b/configTemplate.json
index 30a698fe4..476215036 100644
--- a/configTemplate.json
+++ b/configTemplate.json
@@ -35,14 +35,49 @@
"isSensitive" : false,
"description" : "Your asound settings",
"beforeUpdate": "injectAsound",
- "category" : "system"
+ "category" : "audio",
+ "parent" : {
+ "config" : "disableSoundAndMic",
+ "condition": "isnot",
+ "value" : true
+ }
},
"recordAudioAfterWakeword": {
"defaultValue": false,
"dataType" : "boolean",
"isSensitive" : false,
"description" : "Allow audio record after a wakeword is detected to keep the last user speech. Can be usefull for recording skills",
- "category" : "system"
+ "category" : "audio"
+ },
+ "outputDevice" : {
+ "defaultValue": "",
+ "dataType" : "list",
+ "isSensitive" : false,
+ "values" : [],
+ "description" : "The device to use to play sounds",
+ "category" : "audio",
+ "onInit" : "populateAudioOutputConfig",
+ "onUpdate" : "AudioServer.updateAudioDevices",
+ "parent" : {
+ "config" : "disableSoundAndMic",
+ "condition": "isnot",
+ "value" : true
+ }
+ },
+ "inputDevice" : {
+ "defaultValue": "",
+ "dataType" : "list",
+ "isSensitive" : false,
+ "values" : [],
+ "description" : "The device to use to record sounds",
+ "category" : "audio",
+ "onInit" : "populateAudioInputConfig",
+ "onUpdate" : "AudioServer.updateAudioDevices",
+ "parent" : {
+ "config" : "disableSoundAndMic",
+ "condition": "isnot",
+ "value" : true
+ }
},
"deviceName" : {
"defaultValue": "default",
@@ -72,45 +107,60 @@
"dataType" : "string",
"isSensitive" : true,
"description" : "API key for IBM Cloud Watson Tts and ASR",
- "category" : "credentials"
+ "category" : "credentials",
+ "parent" : {
+ "config" : "stayCompletlyOffline",
+ "condition": "is",
+ "value" : false
+ }
},
"ibmCloudAPIURL" : {
"defaultValue": "",
"dataType" : "string",
"isSensitive" : false,
"description" : "API url for IBM Cloud Watson Tts and ASR",
- "category" : "credentials"
+ "category" : "credentials",
+ "parent" : {
+ "config" : "stayCompletlyOffline",
+ "condition": "is",
+ "value" : false
+ }
},
"autoReportSkillErrors" : {
"defaultValue": false,
"dataType" : "boolean",
"isSensitive" : false,
"description" : "If true, an error thrown by a skill will automatically post a github issue and ping the author",
- "category" : "system"
+ "category" : "system",
+ "parent" : {
+ "config" : "stayCompletlyOffline",
+ "condition": "is",
+ "value" : false
+ }
},
- "disableSoundAndMic" : {
+ "disableSoundAndMic" : {
"defaultValue": false,
"dataType" : "boolean",
"isSensitive" : false,
"description" : "If this device is a server without sound and mic, turn this to true",
"onUpdate" : "enableDisableSound",
- "category" : "device"
+ "category" : "audio"
},
- "notUnderstoodRetries": {
+ "notUnderstoodRetries" : {
"defaultValue": 3,
"dataType" : "integer",
"isSensitive" : false,
"description" : "Defines how many times Alice will ask to repeat if not understood before she gives up",
"category" : "system"
},
- "ssid" : {
+ "ssid" : {
"defaultValue": "",
"dataType" : "string",
"isSensitive" : false,
"description" : "Your Wifi name",
"category" : "device"
},
- "debug" : {
+ "debug" : {
"defaultValue": false,
"dataType" : "boolean",
"isSensitive" : false,
@@ -118,14 +168,14 @@
"onUpdate" : "toggleDebugLogs",
"category" : "system"
},
- "advancedDebug" : {
+ "advancedDebug" : {
"defaultValue": false,
"dataType" : "boolean",
"isSensitive" : false,
"description" : "If true advanced debugging will be enabled. This activates extra information and tools for debugging.",
"category" : "advanced debug"
},
- "memoryProfiling" : {
+ "memoryProfiling" : {
"defaultValue": false,
"dataType" : "boolean",
"isSensitive" : false,
@@ -137,7 +187,7 @@
"value" : true
}
},
- "databaseProfiling" : {
+ "databaseProfiling" : {
"defaultValue": false,
"dataType" : "boolean",
"isSensitive" : false,
@@ -149,14 +199,14 @@
"value" : true
}
},
- "wifipassword" : {
+ "wifipassword" : {
"defaultValue": "",
"dataType" : "string",
"isSensitive" : true,
"description" : "Your Wifi password",
"category" : "device"
},
- "mqttHost" : {
+ "mqttHost" : {
"defaultValue": "localhost",
"dataType" : "string",
"isSensitive" : false,
@@ -164,7 +214,7 @@
"onUpdate" : "updateMqttSettings",
"category" : "mqtt"
},
- "mqttPort" : {
+ "mqttPort" : {
"defaultValue": 1883,
"dataType" : "integer",
"isSensitive" : false,
@@ -408,14 +458,24 @@
"dataType" : "string",
"isSensitive" : true,
"description" : "Your Amazon services access key",
- "category" : "credentials"
+ "category" : "credentials",
+ "parent" : {
+ "config" : "stayCompletlyOffline",
+ "condition": "is",
+ "value" : false
+ }
},
"awsSecretKey" : {
"defaultValue": "",
"dataType" : "string",
"isSensitive" : true,
"description" : "Your Amazon services secret key",
- "category" : "credentials"
+ "category" : "credentials",
+ "parent" : {
+ "config" : "stayCompletlyOffline",
+ "condition": "is",
+ "value" : false
+ }
},
"useHLC" : {
"defaultValue": false,
@@ -433,7 +493,8 @@
"fr",
"de",
"it",
- "pt"
+ "pt",
+ "pl"
],
"description" : "Project Alice active language",
"category" : "system"
@@ -450,42 +511,72 @@
"dataType" : "string",
"isSensitive" : false,
"description" : "If you want to use a non natively supported language, set it here.",
- "category" : "system"
+ "category" : "system",
+ "parent" : {
+ "config" : "stayCompletlyOffline",
+ "condition": "is",
+ "value" : false
+ }
},
"nonNativeSupportCountry" : {
"defaultValue": "",
"dataType" : "string",
"isSensitive" : false,
"description" : "If you want to use a non natively supported country, set it here.",
- "category" : "system"
+ "category" : "system",
+ "parent" : {
+ "config" : "stayCompletlyOffline",
+ "condition": "is",
+ "value" : false
+ }
},
"aliceAutoUpdate" : {
"defaultValue": false,
"dataType" : "boolean",
"isSensitive" : false,
"description" : "Whether Alice should auto update, checked every hour",
- "category" : "system"
+ "category" : "system",
+ "parent" : {
+ "config" : "stayCompletlyOffline",
+ "condition": "is",
+ "value" : false
+ }
},
"skillAutoUpdate" : {
"defaultValue": false,
"dataType" : "boolean",
"isSensitive" : false,
"description" : "Whether skills should auto update, checked every 15 minutes",
- "category" : "system"
+ "category" : "system",
+ "parent" : {
+ "config" : "stayCompletlyOffline",
+ "condition": "is",
+ "value" : false
+ }
},
"githubUsername" : {
"defaultValue": "",
"dataType" : "string",
"isSensitive" : false,
"description" : "Not mendatory, your github username and token allows you to use Github API much more, such as checking for skills, updating them etc etc",
- "category" : "credentials"
+ "category" : "credentials",
+ "parent" : {
+ "config" : "stayCompletlyOffline",
+ "condition": "is",
+ "value" : false
+ }
},
"githubToken" : {
"defaultValue": "",
"dataType" : "string",
"isSensitive" : true,
"description" : "Not mendatory, your github username and token allows you to use Github API much more, such as checking for skills, updating them etc etc",
- "category" : "credentials"
+ "category" : "credentials",
+ "parent" : {
+ "config" : "stayCompletlyOffline",
+ "condition": "is",
+ "value" : false
+ }
},
"aliceUpdateChannel" : {
"defaultValue": "master",
@@ -548,6 +639,13 @@
"IT",
"CH"
]
+ },
+ "pl": {
+ "default" : false,
+ "defaultCountryCode": "PL",
+ "countryCodes" : [
+ "PL"
+ ]
}
},
"dataType" : "list",
@@ -585,6 +683,13 @@
"IT",
"CH"
]
+ },
+ "pl": {
+ "default" : false,
+ "defaultCountryCode": "PL",
+ "countryCodes" : [
+ "PL"
+ ]
}
},
"display" : "hidden",
@@ -649,11 +754,58 @@
"description" : "Change the web interface port to be used",
"category" : "system"
},
+ "scenariosActive" : {
+ "defaultValue": false,
+ "dataType" : "boolean",
+ "isSensitive" : false,
+ "description" : "Activates the scenarios support on the webinterface, using Node-RED.",
+ "category" : "scenarios",
+ "onUpdate" : "NodeRedManager.toggle"
+ },
+ "dontStopNodeRed" : {
+ "defaultValue": false,
+ "dataType" : "boolean",
+ "isSensitive" : false,
+ "description" : "If activated, Node-RED won't be stopped when Alice is shut down.",
+ "category" : "scenarios",
+ "parent" : {
+ "config" : "scenariosActive",
+ "condition": "is",
+ "value" : true
+ }
+ },
"devMode" : {
"defaultValue": false,
"dataType" : "boolean",
"isSensitive" : false,
"description" : "Activates the developer part of the interface, for skill development",
"category" : "system"
+ },
+ "suggestSkillsToInstall" : {
+ "defaultValue": false,
+ "dataType" : "boolean",
+ "isSensitive" : false,
+ "description" : "If enabled, whenever something you say is not recognized, Alice will try to propose a skill that can do it.",
+ "category" : "system",
+ "parent" : {
+ "config" : "stayCompletlyOffline",
+ "condition": "is",
+ "value" : false
+ }
+ },
+ "internetQuality" : {
+ "defaultValue": 10,
+ "dataType" : "range",
+ "min" : 1,
+ "max" : 10,
+ "step" : 1,
+ "isSensitive" : false,
+ "description" : "How would you rate your internet connection QUALITY? 0 = drops all the time, 10 = Very stable",
+ "category" : "system",
+ "parent" : {
+ "config" : "stayCompletlyOffline",
+ "condition": "is",
+ "value" : false
+ }
}
}
diff --git a/core/Initializer.py b/core/Initializer.py
index 9f7dc5f9d..1cb0c9d77 100644
--- a/core/Initializer.py
+++ b/core/Initializer.py
@@ -26,7 +26,10 @@ def __init__(self, default: dict):
def __getitem__(self, item):
try:
- return super().__getitem__(item) or ''
+ item = super().__getitem__(item)
+ if not item:
+ raise Exception
+ return item
except:
print(f'Missing key **{item}** in provided yaml file.')
return ''
@@ -83,9 +86,10 @@ def start(self):
if not self.confsFile.exists() and self.oldConfFile.exists():
self._logger.logFatal('Found old conf file, trying to migrate...')
try:
+ # noinspection PyPackageRequirements,PyUnresolvedReferences
import config.py
- self.confsFile.write_text(json.dumps(config.settings, indent=4, ensure_ascii=False, sort_keys=True))
+ self.confsFile.write_text(json.dumps(config.settings, indent='\t', ensure_ascii=False, sort_keys=True))
except:
self._logger.logFatal('Something went wrong migrating the old configs, aborting')
return False
@@ -137,17 +141,13 @@ def loadConfig(self) -> dict:
try:
# noinspection PyUnboundLocalVariable
load = yaml.safe_load(f)
- if not load:
- raise yaml.YAMLError
-
initConfs = InitDict(load)
+ # Check that we are running using the latest yaml
+ if float(initConfs['version']) < VERSION:
+ self._logger.logFatal('The yaml file you are using is deprecated. Please update it before trying again')
+
except yaml.YAMLError as e:
self._logger.logFatal(f'Failed loading init configurations: {e}')
- return dict()
-
- # Check that we are running using the latest yaml
- if float(initConfs['version']) < VERSION:
- self._logger.logFatal('The yaml file you are using is deprecated. Please update it before trying again')
return initConfs
@@ -359,7 +359,7 @@ def initProjectAlice(self) -> bool: # NOSONAR
elif not self._confsFile.exists() and self._confsSample.exists():
self._logger.logWarning('No config file found, creating it from sample file')
- self._confsFile.write_text(json.dumps({configName: configData['defaultValue'] for configName, configData in json.loads(self._confsSample.read_text()).items()}, indent=4, ensure_ascii=False))
+ self._confsFile.write_text(json.dumps({configName: configData['defaultValue'] for configName, configData in json.loads(self._confsSample.read_text()).items()}, indent='\t', ensure_ascii=False))
elif self._confsFile.exists() and initConfs['forceRewrite']:
self._logger.logWarning('Config file found and force rewrite specified, let\'s restart all this!')
@@ -367,7 +367,7 @@ def initProjectAlice(self) -> bool: # NOSONAR
self._logger.logFatal('Unfortunately it won\'t be possible, config sample is not existing')
return False
- self._confsFile.write_text(json.dumps(self._confsSample.read_text(), indent=4))
+ self._confsFile.write_text(json.dumps(self._confsSample.read_text(), indent='\t'))
try:
@@ -537,6 +537,11 @@ def initProjectAlice(self) -> bool: # NOSONAR
audioHardware = hardware
break
+ if not audioHardware:
+ confs['disableSoundAndMic'] = True
+ else:
+ confs['disableSoundAndMic'] = False
+
hlcServiceFilePath = Path('/etc/systemd/system/hermesledcontrol.service')
if initConfs['useHLC']:
@@ -656,7 +661,7 @@ def initProjectAlice(self) -> bool: # NOSONAR
sort = dict(sorted(confs.items()))
try:
- self._confsFile.write_text(json.dumps(sort, indent=4))
+ self._confsFile.write_text(json.dumps(sort, indent='\t'))
except Exception as e:
self._logger.logFatal(f'An error occured while writting final configuration file: {e}')
diff --git a/core/asr/ASRManager.py b/core/asr/ASRManager.py
index 4d8d7638e..2d0236032 100644
--- a/core/asr/ASRManager.py
+++ b/core/asr/ASRManager.py
@@ -23,6 +23,7 @@ def __init__(self):
self._asr = None
self._streams: Dict[str, Recorder] = dict()
self._translator = Translator()
+ self._usingFallback = False
def onStart(self):
@@ -98,7 +99,8 @@ def asr(self) -> Asr:
def onInternetConnected(self):
- if self.ConfigManager.getAliceConfigByName('stayCompletlyOffline') or self.ConfigManager.getAliceConfigByName('keepASROffline'):
+ if self.ConfigManager.getAliceConfigByName('stayCompletlyOffline') or self.ConfigManager.getAliceConfigByName('keepASROffline') or \
+ self.ConfigManager.getAliceConfigByName('asrFallback') == self.ConfigManager.getAliceConfigByName('asr'):
return
if not self._asr.isOnlineASR:
diff --git a/core/asr/model/GoogleAsr.py b/core/asr/model/GoogleAsr.py
index 69b3ab527..189267c1f 100644
--- a/core/asr/model/GoogleAsr.py
+++ b/core/asr/model/GoogleAsr.py
@@ -1,5 +1,7 @@
import os
from pathlib import Path
+from threading import Event
+from time import time
from typing import Generator, Optional
from core.asr.model.ASRResult import ASRResult
@@ -9,6 +11,7 @@
from core.util.Stopwatch import Stopwatch
try:
+ # noinspection PyUnresolvedReferences,PyPackageRequirements
from google.cloud.speech import SpeechClient, enums, types
except:
pass # Auto installed
@@ -34,8 +37,11 @@ def __init__(self):
self._client: Optional[SpeechClient] = None
self._streamingConfig: Optional[types.StreamingRecognitionConfig] = None
- self._previousCapture = ''
+ self._internetLostFlag = Event() # Set if internet goes down, cut the decoding
+ self._lastResultCheck = 0 # The time the intermediate results were last checked. If actual time is greater than this value + 3, stop processing, internet issues
+ self._previousCapture = '' # The text that was last captured in the iteration
+ self._delayedGoogleConfirmation = False # set whether slow internet is detected or not
def onStart(self):
super().onStart()
@@ -68,6 +74,7 @@ def decodeStream(self, session: DialogSession) -> Optional[ASRResult]:
responses = self._client.streaming_recognize(self._streamingConfig, requests)
result = self._checkResponses(session, responses)
except:
+ self._internetLostFlag.clear()
self.logWarning('Failed ASR request')
self.end()
@@ -80,11 +87,24 @@ def decodeStream(self, session: DialogSession) -> Optional[ASRResult]:
) if result else None
+ def onInternetLost(self):
+ self._internetLostFlag.set()
+
+
def _checkResponses(self, session: DialogSession, responses: Generator) -> Optional[tuple]:
if responses is None:
return None
for response in responses:
+ if self._internetLostFlag.is_set():
+ self.logDebug('Internet connectivity lost during ASR decoding')
+
+ if not response.results:
+ raise Exception('Internet connectivity lost during decoding')
+
+ result = response.results[0]
+ return result.alternatives[0].transcript, result.alternatives[0].confidence
+
if not response.results:
continue
@@ -93,9 +113,36 @@ def _checkResponses(self, session: DialogSession, responses: Generator) -> Optio
continue
if result.is_final:
+ self._lastResultCheck = 0
+ self._delayedGoogleConfirmation = False
+ # print(f'Text confirmed by Google')
return result.alternatives[0].transcript, result.alternatives[0].confidence
elif result.alternatives[0].transcript != self._previousCapture:
self.partialTextCaptured(session=session, text=result.alternatives[0].transcript, likelihood=result.alternatives[0].confidence, seconds=0)
- self._previousCapture = result.alternatives[0].transcript
+ # below function captures the "potential" full utterance not just one word from it
+ if len(self._previousCapture) <= len(result.alternatives[0].transcript):
+ self._previousCapture = result.alternatives[0].transcript
+ elif result.alternatives[0].transcript == self._previousCapture:
+
+ # If we are here it's cause google hasn't responded yet with confirmation on captured text
+ # Store the time in seconds since epoch
+ now = int(time())
+ # Set a reference to nows time plus 3 seconds
+ self._lastResultCheck = now + 3
+ # wait 3 seconds and see if google responds
+ if not self._delayedGoogleConfirmation:
+ # print(f'Text of "{self._previousCapture}" captured but not confirmed by GoogleASR yet')
+ while now <= self._lastResultCheck:
+ now = int(time())
+ self._delayedGoogleConfirmation = True
+ # Give google the option to still process the utterance
+ continue
+ # During next iteration, If google hasn't responded in 3 seconds assume intent is correct
+ if self._delayedGoogleConfirmation:
+ self.logDebug(f'Stopping process as there seems to be connectivity issues')
+ self._lastResultCheck = 0
+ self._delayedGoogleConfirmation = False
+ return result.alternatives[0].transcript, result.alternatives[0].confidence
return None
+
diff --git a/core/asr/model/PocketSphinxAsr.py b/core/asr/model/PocketSphinxAsr.py
index 87b7cc41f..35dfd8252 100644
--- a/core/asr/model/PocketSphinxAsr.py
+++ b/core/asr/model/PocketSphinxAsr.py
@@ -89,7 +89,7 @@ def downloadLanguage(self, forceLang: str = '') -> bool:
return False
else:
# TODO be universal
- self.downloadLanguage(forceLang='eu-US')
+ self.downloadLanguage(forceLang='en-US')
else:
if download.suffix == '.tar':
dest = Path(venv, 'model', lang.lower())
diff --git a/core/asr/model/SnipsAsr.py b/core/asr/model/SnipsAsr.py
index c021e0cc9..558948bc6 100644
--- a/core/asr/model/SnipsAsr.py
+++ b/core/asr/model/SnipsAsr.py
@@ -18,9 +18,10 @@ class SnipsAsr(Asr):
'system': [
'libgfortran3'
],
- 'pip' : []
+ 'pip' : []
}
+
def __init__(self):
super().__init__()
self._capableOfArbitraryCapture = True
@@ -28,6 +29,12 @@ def __init__(self):
self._listening = False
+ def installDependencies(self):
+ super().installDependencies()
+ self.Commons.runRootSystemCommand(['systemctl', 'stop', 'snips-asr'])
+ self.Commons.runRootSystemCommand(['systemctl', 'disable', 'snips-asr'])
+
+
def onStartListening(self, session):
self._listening = True
diff --git a/core/base/AssistantManager.py b/core/base/AssistantManager.py
index 10a200264..b6e37d4d0 100644
--- a/core/base/AssistantManager.py
+++ b/core/base/AssistantManager.py
@@ -31,7 +31,14 @@ def clearAssistant(self):
def checkAssistant(self, forceRetrain: bool = False):
self.logInfo('Checking assistant')
- if not self.checkConsistency() or forceRetrain:
+
+ if forceRetrain:
+ self.logInfo('Forced assistant training')
+ self.train()
+ elif not self._assistantPath.exists():
+ self.logInfo('Assistant not found')
+ self.train()
+ elif not self.checkConsistency():
self.logInfo('Assistant is not consistent, it needs training')
self.train()
@@ -43,9 +50,6 @@ def checkAssistant(self, forceRetrain: bool = False):
def checkConsistency(self) -> bool:
- if not self._assistantPath.exists() or not self.DialogTemplateManager.checkData():
- return False
-
existingIntents: Dict[str, dict] = dict()
existingSlots: Dict[str, set] = dict()
@@ -196,7 +200,7 @@ def train(self):
assistant['intents'] = [intent for intent in intents.values()]
- self._assistantPath.write_text(json.dumps(assistant, ensure_ascii=False, indent=4, sort_keys=True))
+ self._assistantPath.write_text(json.dumps(assistant, ensure_ascii=False, indent='\t', sort_keys=True))
self.linkAssistant()
self.broadcast(method='snipsAssistantInstalled', exceptions=[self.name], propagateToSkills=True)
diff --git a/core/base/ConfigManager.py b/core/base/ConfigManager.py
index 8ce65615f..f132e6914 100644
--- a/core/base/ConfigManager.py
+++ b/core/base/ConfigManager.py
@@ -4,6 +4,7 @@
import typing
from pathlib import Path
+import sounddevice as sd
import toml
from core.ProjectAliceExceptions import ConfigurationUpdateFailed, VitalConfigMissing
@@ -44,6 +45,21 @@ def onStart(self):
if conf not in self._aliceConfigurations or self._aliceConfigurations[conf] == '':
raise VitalConfigMissing(conf)
+ for setting, definition in {**self._aliceTemplateConfigurations, **self._skillsTemplateConfigurations}.items():
+ function = definition.get('onStart', None)
+ if function:
+ try:
+ if '.' in function:
+ self.logWarning(f'Use of manager for configuration **onStart** for config "{setting}" is not allowed')
+ function = function.split('.')[-1]
+
+ func = getattr(self, function)
+ func()
+ except AttributeError:
+ self.logWarning(f'Configuration onStart method **{function}** does not exist')
+ except Exception as e:
+ self.logError(f'Configuration onStart method **{function}** failed: {e}')
+
def _loadCheckAndUpdateAliceConfigFile(self):
self.logInfo('Checking Alice configuration file')
@@ -57,7 +73,7 @@ def _loadCheckAndUpdateAliceConfigFile(self):
if not aliceConfigs:
self.logInfo('Creating config file from config template')
aliceConfigs = {configName: configData['defaultValue'] if 'defaultValue' in configData else configData for configName, configData in self._aliceTemplateConfigurations.items()}
- self.CONFIG_FILE.write_text(json.dumps(aliceConfigs, indent=4, ensure_ascii=False))
+ self.CONFIG_FILE.write_text(json.dumps(aliceConfigs, indent='\t', ensure_ascii=False))
changes = False
@@ -82,7 +98,7 @@ def _loadCheckAndUpdateAliceConfigFile(self):
if setting == 'supportedLanguages':
continue
- if definition['dataType'] != 'list' and definition['dataType'] != 'longstring':
+ if definition['dataType'] != 'list' and definition['dataType'] != 'longstring' and 'onInit' not in definition:
if not isinstance(aliceConfigs[setting], type(definition['defaultValue'])):
changes = True
try:
@@ -93,14 +109,29 @@ def _loadCheckAndUpdateAliceConfigFile(self):
# If casting failed let's fall back to the new default value
self.logWarning(f'Existing configuration type missmatch: **{setting}**, replaced with template configuration')
aliceConfigs[setting] = definition['defaultValue']
- elif definition['dataType'] == 'list':
+ elif definition['dataType'] == 'list' and 'onInit' not in definition:
values = definition['values'].values() if isinstance(definition['values'], dict) else definition['values']
- if aliceConfigs[setting] not in values:
+ if aliceConfigs[setting] and aliceConfigs[setting] not in values:
changes = True
self.logWarning(f'Selected value **{aliceConfigs[setting]}** for setting **{setting}** doesn\'t exist, reverted to default value --{definition["defaultValue"]}--')
aliceConfigs[setting] = definition['defaultValue']
+ function = definition.get('onInit', None)
+ if function:
+ try:
+ if '.' in function:
+ self.logWarning(f'Use of manager for configuration **onInit** for config "{setting}" is not allowed')
+ function = function.split('.')[-1]
+
+ func = getattr(self, function)
+ func()
+ except AttributeError:
+ self.logWarning(f'Configuration onInit method **{function}** does not exist')
+ except Exception as e:
+ self.logError(f'Configuration onInit method **{function}** failed: {e}')
+
+
# Setting logger level immediately
if aliceConfigs['advancedDebug'] and not aliceConfigs['debug']:
aliceConfigs['debug'] = True
@@ -122,6 +153,14 @@ def _loadCheckAndUpdateAliceConfigFile(self):
self._aliceConfigurations = aliceConfigs
+ def updateAliceConfigDefinitionValues(self, setting: str, value: typing.Any):
+ if setting not in self._aliceTemplateConfigurations:
+ self.logWarning(f'Was asked to update **{setting}** from config templates, but setting doesn\'t exist')
+ return
+
+ self._aliceTemplateConfigurations[setting]['values'] = value
+
+
@staticmethod
def loadJsonFromFile(jsonFile: Path) -> dict:
try:
@@ -131,13 +170,14 @@ def loadJsonFromFile(jsonFile: Path) -> dict:
raise
- def updateAliceConfiguration(self, key: str, value: typing.Any):
+ def updateAliceConfiguration(self, key: str, value: typing.Any, dump: bool = True):
"""
Updating a core config is sensitive, if the request comes from a skill.
First check if the request came from a skill at anytime and if so ask permission
to the user
:param key: str
:param value: str
+ :param dump: bool If set to False, the configs won't be dumped to the json file
:return: None
"""
@@ -167,8 +207,18 @@ def updateAliceConfiguration(self, key: str, value: typing.Any):
self.logWarning(f'Was asked to update **{key}** but key doesn\'t exist')
raise ConfigurationUpdateFailed()
+ pre = self.getAliceConfUpdatePreProcessing(key)
+ if pre and not self.ConfigManager.doConfigUpdatePreProcessing(pre, value):
+ return
+
self._aliceConfigurations[key] = value
- self.writeToAliceConfigurationFile()
+
+ if dump:
+ self.writeToAliceConfigurationFile()
+
+ pp = self.ConfigManager.getAliceConfUpdatePostProcessing(key)
+ if pp:
+ self.ConfigManager.doConfigUpdatePostProcessing(pp)
def bulkUpdateAliceConfigurations(self):
@@ -179,7 +229,7 @@ def bulkUpdateAliceConfigurations(self):
if key not in self._aliceConfigurations:
self.logWarning(f'Was asked to update **{key}** but key doesn\'t exist')
continue
- self._aliceConfigurations[key] = value
+ self.updateAliceConfiguration(key, value, False)
self.writeToAliceConfigurationFile()
self.deletePendingAliceConfigurationUpdates()
@@ -276,7 +326,7 @@ def writeToAliceConfigurationFile(self, confs: dict = None):
self._aliceConfigurations = sort
try:
- self.CONFIG_FILE.write_text(json.dumps(sort, indent=4, sort_keys=True))
+ self.CONFIG_FILE.write_text(json.dumps(sort, indent='\t', sort_keys=True))
except Exception:
raise ConfigurationUpdateFailed()
@@ -293,7 +343,7 @@ def _writeToSkillConfigurationFile(self, skillName: str, confs: dict):
confsCleaned = {key: value for key, value in confs.items() if key not in misterProper}
skillConfigFile = Path(self.Commons.rootDir(), 'skills', skillName, 'config.json')
- skillConfigFile.write_text(json.dumps(confsCleaned, indent=4, ensure_ascii=False, sort_keys=True))
+ skillConfigFile.write_text(json.dumps(confsCleaned, indent='\t', ensure_ascii=False, sort_keys=True))
def loadSnipsConfigurations(self) -> dict:
@@ -366,6 +416,14 @@ def getAliceConfigByName(self, configName: str) -> typing.Any:
return ''
+ def getAliceConfigTemplateByName(self, configName: str) -> typing.Any:
+ if configName in self._aliceTemplateConfigurations:
+ return self._aliceTemplateConfigurations[configName]
+ else:
+ self.logDebug(f'Trying to get config template **{configName}** but it does not exist')
+ return ''
+
+
def getSkillConfigByName(self, skillName: str, configName: str) -> typing.Any:
return self._skillsConfigurations.get(skillName, dict()).get(configName, None)
@@ -501,7 +559,18 @@ def getAliceConfUpdatePostProcessing(self, confName: str) -> typing.Optional[str
def doConfigUpdatePreProcessing(self, function: str, value: typing.Any) -> bool:
# Call alice config pre processing functions.
try:
- func = getattr(self, function)
+ if '.' in function:
+ manager, function = function.split('.')
+
+ try:
+ mngr = getattr(self, manager)
+ except AttributeError:
+ self.logWarning(f'Config pre processing manager **{manager}** does not exist')
+ return False
+ else:
+ mngr = self
+
+ func = getattr(mngr, function)
except AttributeError:
self.logWarning(f'Configuration pre processing method **{function}** does not exist')
return False
@@ -513,12 +582,27 @@ def doConfigUpdatePreProcessing(self, function: str, value: typing.Any) -> bool:
return False
- def doConfigUpdatePostProcessing(self, functions: set):
+ def doConfigUpdatePostProcessing(self, functions: typing.Union[str, set]):
# Call alice config post processing functions. This will call methods that are needed after a certain setting was
# updated while Project Alice was running
+
+ if isinstance(functions, str):
+ functions = {functions}
+
for function in functions:
try:
- func = getattr(self, function)
+ if '.' in function:
+ manager, function = function.split('.')
+
+ try:
+ mngr = getattr(self, manager)
+ except AttributeError:
+ self.logWarning(f'Config post processing manager **{manager}** does not exist')
+ return False
+ else:
+ mngr = self
+
+ func = getattr(mngr, function)
except AttributeError:
self.logWarning(f'Configuration post processing method **{function}** does not exist')
continue
@@ -532,9 +616,9 @@ def doConfigUpdatePostProcessing(self, functions: set):
def updateMqttSettings(self):
self.ConfigManager.updateSnipsConfiguration('snips-common', 'mqtt', f'{self.getAliceConfigByName("mqttHost")}:{self.getAliceConfigByName("mqttPort"):}', False, True)
- self.ConfigManager.updateSnipsConfiguration('snips-common', 'mqtt_username', self.getAliceConfigByName('mqttHost'), False, True)
- self.ConfigManager.updateSnipsConfiguration('snips-common', 'mqtt_password', self.getAliceConfigByName('mqttHost'), False, True)
- self.ConfigManager.updateSnipsConfiguration('snips-common', 'mqtt_tls_cafile', self.getAliceConfigByName('mqttHost'), True, True)
+ self.ConfigManager.updateSnipsConfiguration('snips-common', 'mqtt_username', self.getAliceConfigByName('mqttUser'), False, True)
+ self.ConfigManager.updateSnipsConfiguration('snips-common', 'mqtt_password', self.getAliceConfigByName('mqttPassword'), False, True)
+ self.ConfigManager.updateSnipsConfiguration('snips-common', 'mqtt_tls_cafile', self.getAliceConfigByName('mqttTLSFile'), True, True)
self.reconnectMqtt()
@@ -617,6 +701,36 @@ def getGithubAuth(self) -> tuple:
return (username, token) if (username and token) else None
+ def populateAudioInputConfig(self):
+ try:
+ devices = self._listAudioDevices()
+ self.updateAliceConfigDefinitionValues(setting='inputDevice', value=devices)
+ except:
+ if not self.getAliceConfigByName('disableSoundAndMic'):
+ self.logWarning('No audio input device found')
+
+
+ def populateAudioOutputConfig(self):
+ try:
+ devices = self._listAudioDevices()
+ self.updateAliceConfigDefinitionValues(setting='outputDevice', value=devices)
+ except:
+ if not self.getAliceConfigByName('disableSoundAndMic'):
+ self.logWarning('No audio output device found')
+
+
+ @staticmethod
+ def _listAudioDevices() -> list:
+ try:
+ devices = [device['name'] for device in sd.query_devices()]
+ if not devices:
+ raise Exception
+ except:
+ raise
+
+ return devices
+
+
@property
def snipsConfigurations(self) -> dict:
return self._snipsConfigurations
diff --git a/core/base/SkillManager.py b/core/base/SkillManager.py
index fbd3bf897..fe2d62cc0 100644
--- a/core/base/SkillManager.py
+++ b/core/base/SkillManager.py
@@ -34,7 +34,8 @@ class SkillManager(Manager):
DATABASE = {
'skills' : [
'skillName TEXT NOT NULL UNIQUE',
- 'active INTEGER NOT NULL DEFAULT 1'
+ 'active INTEGER NOT NULL DEFAULT 1',
+ 'scenarioVersion TEXT NOT NULL DEFAULT "0.0.0"',
],
'widgets': [
'parent TEXT NOT NULL UNIQUE',
@@ -69,6 +70,7 @@ def __init__(self):
self._postBootSkillActions = dict()
self._widgets = dict()
+ self._widgetsByIndex = dict()
def onStart(self):
@@ -95,6 +97,7 @@ def onStart(self):
self.ConfigManager.loadCheckAndUpdateSkillConfigurations()
self.startAllSkills()
+ self.sortWidgetZIndexes()
# noinspection SqlResolve
@@ -173,7 +176,7 @@ def changeSkillStateInDB(self, skillName: str, newState: bool):
'state' : 0,
'posx' : 0,
'posy' : 0,
- 'zindex': 9999
+ 'zindex': -1
},
row=('parent', skillName)
)
@@ -231,21 +234,31 @@ def onAssistantInstalled(self, **kwargs):
def sortWidgetZIndexes(self):
- widgets = dict()
+ # Create a list of skills with their z index as key
+ self._widgetsByIndex = dict()
for skillName, widgetList in self._widgets.items():
for widget in widgetList.values():
- widgets[int(widget.zindex)] = widget
+ if widget.state != 1:
+ continue
+
+ if int(widget.zindex) not in self._widgetsByIndex:
+ self._widgetsByIndex[int(widget.zindex)] = widget
+ else:
+ i = 1000
+ while True:
+ if i not in self._widgetsByIndex:
+ self._widgetsByIndex[i] = widget
+ break
+ i += 1
+
+ # Rewrite a logical zindex flow
+ for i, widget in enumerate(self._widgetsByIndex.values()):
+ widget.zindex = i
+ widget.saveToDB()
- counter = 0
- for i in sorted(widgets.keys()):
- if widgets[i].state == 0:
- widgets[i].zindex = -1
- widgets[i].saveToDB()
- continue
- widgets[i].zindex = counter
- counter += 1
- widgets[i].saveToDB()
+ def nextZIndex(self) -> int:
+ return len(self._widgetsByIndex)
@property
@@ -608,7 +621,7 @@ def _checkForSkillInstall(self):
root = Path(self.Commons.rootDir(), constants.SKILL_INSTALL_TICKET_PATH)
files = [f for f in root.iterdir() if f.suffix == '.install']
- if self._busyInstalling.isSet() or not files or self.ProjectAlice.restart or self.ProjectAlice.updating:
+ if self._busyInstalling.isSet() or not files or self.ProjectAlice.restart or self.ProjectAlice.updating or self.NluManager.training:
return
self.logInfo(f'Found {len(files)} install ticket', plural='ticket')
@@ -913,6 +926,19 @@ def allScenarioNodes(self) -> Dict[str, tuple]:
return ret
+ def getSkillScenarioVersion(self, skillName: str) -> Version:
+ if skillName not in self._skillList:
+ return Version.fromString('0.0.0')
+ else:
+ # noinspection SqlResolve
+ query = 'SELECT * FROM :__table__ WHERE skillName = :skillName'
+ data = self.DatabaseManager.fetch(tableName='skills', query=query, values={'skillName': skillName}, callerName=self.name)
+ if not data:
+ return Version.fromString('0.0.0')
+
+ return Version.fromString(data['scenarioVersion'])
+
+
def wipeSkills(self, addDefaults: bool = True):
shutil.rmtree(Path(self.Commons.rootDir(), 'skills'))
Path(self.Commons.rootDir(), 'skills').mkdir()
@@ -938,21 +964,7 @@ def createNewSkill(self, skillDefinition: dict) -> bool:
try:
self.logInfo(f'Creating new skill "{skillDefinition["name"]}"')
- rootDir = Path(self.Commons.rootDir()) / 'skills'
- skillTemplateDir = rootDir / 'skill_DefaultTemplate'
-
- if skillTemplateDir.exists():
- shutil.rmtree(skillTemplateDir)
-
- self.Commons.runSystemCommand(['git', '-C', str(rootDir), 'clone', f'{constants.GITHUB_URL}/skill_DefaultTemplate.git'])
-
skillName = skillDefinition['name'][0].upper() + skillDefinition['name'][1:]
- skillDir = rootDir / skillName
-
- skillTemplateDir.rename(skillDir)
-
- installFile = skillDir / f'{skillDefinition["name"]}.install'
- Path(skillDir, 'DefaultTemplate.install').rename(installFile)
supportedLanguages = [
'en'
]
@@ -962,6 +974,8 @@ def createNewSkill(self, skillDefinition: dict) -> bool:
supportedLanguages.append('de')
if skillDefinition['it'] == 'yes':
supportedLanguages.append('it')
+ if skillDefinition['pl'] == 'yes':
+ supportedLanguages.append('pl')
conditions = {
'lang': supportedLanguages
@@ -982,103 +996,28 @@ def createNewSkill(self, skillDefinition: dict) -> bool:
if skillDefinition['conditionActiveManager']:
conditions['activeManager'] = [manager.strip() for manager in skillDefinition['conditionActiveManager'].split(',')]
- installContent = {
- 'name' : skillName,
- 'version' : '0.0.1',
- 'icon' : 'fab fa-battle-net',
- 'category' : 'undefined',
- 'author' : self.ConfigManager.getAliceConfigByName('githubUsername'),
- 'maintainers' : [],
- 'desc' : skillDefinition['description'].capitalize(),
- 'aliceMinVersion' : constants.VERSION,
- 'systemRequirements': [req.strip() for req in skillDefinition['sysreq'].split(',')],
- 'pipRequirements' : [req.strip() for req in skillDefinition['pipreq'].split(',')],
+ data = {
+ 'username' : self.ConfigManager.getAliceConfigByName('githubUsername'),
+ 'skillName' : skillName,
+ 'description' : skillDefinition['description'].capitalize(),
+ 'category' : skillDefinition['category'],
+ 'speakableName' : skillDefinition['speakableName'],
+ 'langs' : supportedLanguages,
+ 'createInstructions': skillDefinition['instructions'],
+ 'pipreq' : [req.strip() for req in skillDefinition['pipreq'].split(',')],
+ 'sysreq' : [req.strip() for req in skillDefinition['sysreq'].split(',')],
+ 'widgets' : [self.Commons.toPascalCase(widget).strip() for widget in skillDefinition['widgets'].split(',')],
+ 'scenarioNodes' : [self.Commons.toPascalCase(node).strip() for node in skillDefinition['nodes'].split(',')],
+ 'outputDestination' : str(Path(self.Commons.rootDir()) / 'skills' / skillName),
'conditions' : conditions
}
- # Install file
- with installFile.open('w') as fp:
- fp.write(json.dumps(installContent, indent=4))
-
- # Dialog templates and talks
- dialogTemplateTemplate = skillDir / 'dialogTemplate/default.json'
- with dialogTemplateTemplate.open() as fp:
- dialogTemplate = json.load(fp)
- dialogTemplate['skill'] = skillName
- dialogTemplate['description'] = skillDefinition['description'].capitalize()
-
- for lang in supportedLanguages:
- with Path(skillDir, f'dialogTemplate/{lang}.json').open('w+') as fp:
- fp.write(json.dumps(dialogTemplate, indent=4))
-
- with Path(skillDir, f'talks/{lang}.json').open('w+') as fp:
- fp.write(json.dumps(dict()))
+ dump = Path(f'/tmp/{skillName}.json')
+ dump.write_text(json.dumps(data, ensure_ascii=False))
- dialogTemplateTemplate.unlink()
-
- # Widgets
- if skillDefinition['widgets']:
- widgetRootDir = skillDir / 'widgets'
- css = widgetRootDir / 'css/widget.css'
- js = widgetRootDir / 'js/widget.js'
- lang = widgetRootDir / 'lang/widget.lang.json'
- html = widgetRootDir / 'templates/widget.html'
- python = widgetRootDir / 'widget.py'
-
- for widget in skillDefinition['widgets'].split(','):
- widgetName = widget.strip()
- widgetName = widgetName[0].upper() + widgetName[1:]
-
- content = css.read_text().replace('%widgetname%', widgetName)
- with Path(widgetRootDir, f'css/{widgetName}.css').open('w+') as fp:
- fp.write(content)
-
- shutil.copy(str(js), str(js).replace('widget.js', f'{widgetName}.js'))
- shutil.copy(str(lang), str(lang).replace('widget.lang.json', f'{widgetName}.lang.json'))
-
- content = html.read_text().replace('%widgetname%', widgetName)
- with Path(widgetRootDir, f'templates/{widgetName}.html').open('w+') as fp:
- fp.write(content)
-
- content = python.read_text().replace('Template(Widget)', f'{widgetName}(Widget)')
- with Path(widgetRootDir, f'{widgetName}.py').open('w+') as fp:
- fp.write(content)
-
- css.unlink()
- js.unlink()
- lang.unlink()
- html.unlink()
- python.unlink()
-
- else:
- shutil.rmtree(str(Path(skillDir, 'widgets')))
-
- languages = ''
- for lang in supportedLanguages:
- languages += f' {lang}\n'
-
- # Readme file
- content = Path(skillDir, 'README.md').read_text().replace('%skillname%', skillName) \
- .replace('%author%', self.ConfigManager.getAliceConfigByName('githubUsername')) \
- .replace('%minVersion%', constants.VERSION) \
- .replace('%description%', skillDefinition['description'].capitalize()) \
- .replace('%languages%', languages)
-
- with Path(skillDir, 'README.md').open('w') as fp:
- fp.write(content)
-
- # Main class
- classFile = skillDir / f'{skillDefinition["name"]}.py'
- Path(skillDir, 'DefaultTemplate.py').rename(classFile)
-
- content = classFile.read_text().replace('%skillname%', skillName) \
- .replace('%author%', self.ConfigManager.getAliceConfigByName('githubUsername')) \
- .replace('%description%', skillDefinition['description'].capitalize())
-
- with classFile.open('w') as fp:
- fp.write(content)
-
- self.logInfo(f'Created "{skillDefinition["name"]}" skill')
+ self.Commons.runSystemCommand(['./venv/bin/pip', '--upgrade', 'projectalice-sk'])
+ self.Commons.runSystemCommand(['./venv/bin/projectalice-sk', 'create', '--file', f'{str(dump)}'])
+ self.logInfo(f'Created **skillName** skill')
return True
except Exception as e:
@@ -1117,7 +1056,7 @@ def uploadSkillToGithub(self, skillName: str, skillDesc: str) -> bool:
self.Commons.runSystemCommand(['git', '-C', str(localDirectory), 'remote', 'add', 'origin', remote])
self.Commons.runSystemCommand(['git', '-C', str(localDirectory), 'add', '--all'])
- self.Commons.runSystemCommand(['git', '-C', str(localDirectory), 'commit', '-m', '"Initial upload"'])
+ self.Commons.runSystemCommand(['git', '-C', str(localDirectory), 'commit', '-m', '"Initial upload by Project Alice Skill Kit"'])
self.Commons.runSystemCommand(['git', '-C', str(localDirectory), 'push', '--set-upstream', 'origin', 'master'])
url = f'https://github.com/{self.ConfigManager.getAliceConfigByName("githubUsername")}/skill_{skillName}.git'
@@ -1137,6 +1076,8 @@ def downloadInstallTicket(self, skillName: str) -> bool:
):
raise Exception
+ requests.get(f'https://skills.projectalice.ch/{skillName}')
+
shutil.move(tmpFile.with_suffix('.tmp'), tmpFile)
return True
except Exception as e:
diff --git a/core/base/SkillStoreManager.py b/core/base/SkillStoreManager.py
index 4ca26005c..790101343 100644
--- a/core/base/SkillStoreManager.py
+++ b/core/base/SkillStoreManager.py
@@ -1,3 +1,5 @@
+import difflib
+from random import shuffle
from typing import Optional
import requests
@@ -6,14 +8,18 @@
from core.base.model.Manager import Manager
from core.base.model.Version import Version
from core.commons import constants
+from core.dialog.model.DialogSession import DialogSession
from core.util.Decorators import Online
class SkillStoreManager(Manager):
+ SUGGESTIONS_DIFF_LIMIT = 0.75
+
def __init__(self):
super().__init__()
self._skillStoreData = dict()
+ self._skillSamplesData = dict()
@property
@@ -25,23 +31,51 @@ def onStart(self):
self.refreshStoreData()
- @Online(catchOnly=True)
def onQuarterHour(self):
self.refreshStoreData()
+ @Online(catchOnly=True)
def refreshStoreData(self):
- updateChannel = self.ConfigManager.getAliceConfigByName('skillsUpdateChannel')
- req = requests.get(url=f'https://skills.projectalice.io/assets/store/{updateChannel}.json')
+ req = requests.get(url=constants.SKILLS_STORE_ASSETS)
if req.status_code not in {200, 304}:
return
self._skillStoreData = req.json()
+ if not self.ConfigManager.getAliceConfigByName('suggestSkillsToInstall'):
+ return
+
+ req = requests.get(url=constants.SKILLS_SAMPLES_STORE_ASSETS)
+ if req.status_code not in {200, 304}:
+ return
+
+ self.prepareSamplesData(req.json())
+
+
+ def prepareSamplesData(self, data: dict):
+ if not data:
+ return
+
+ for skillName, skill in data.items():
+ self._skillSamplesData.setdefault(skillName, skill.get(self.LanguageManager.activeLanguage, list()))
+
def _getSkillUpdateVersion(self, skillName: str) -> Optional[tuple]:
+ """
+ Get the highest skill version number a user can install.
+ This is based on the user preferences, dependending on the current Alice version
+ and the user's selected update channel for skills
+ In case nothing is found, DO NOT FALLBACK TO MASTER
+
+ :param skillName: The skill to look for
+ :return: tuple
+ """
versionMapping = self._skillStoreData.get(skillName, dict()).get('versionMapping', dict())
+ if not versionMapping:
+ raise GithubNotFound
+
userUpdatePref = self.ConfigManager.getAliceConfigByName('skillsUpdateChannel')
skillUpdateVersion = (Version(), '')
@@ -68,6 +102,52 @@ def _getSkillUpdateVersion(self, skillName: str) -> Optional[tuple]:
return skillUpdateVersion
+ def findSkillSuggestion(self, session: DialogSession, string: str = None) -> set:
+ suggestions = set()
+ if not self._skillSamplesData or not self.InternetManager.online:
+ return suggestions
+
+ userInput = session.input if not string else string
+ if not userInput:
+ return suggestions
+
+ for skillName, samples in self._skillSamplesData.items():
+ for sample in samples:
+ diff = difflib.SequenceMatcher(None, userInput, sample).ratio()
+ if diff >= self.SUGGESTIONS_DIFF_LIMIT:
+ suggestions.add(skillName)
+ break
+
+ userInputs = list()
+ userInput = userInput.split()
+
+ if len(userInput) == 1:
+ userInputs.append(userInput.copy())
+
+ for _ in range(max(len(userInput), 8)):
+ shuffle(userInput)
+ userInputs.append(userInput.copy())
+
+ for skillName, samples in self._skillSamplesData.items():
+ for sample in samples:
+ for userInput in userInputs:
+ diff = difflib.SequenceMatcher(None, userInput, sample).ratio()
+ if diff >= self.SUGGESTIONS_DIFF_LIMIT:
+ suggestions.add(skillName)
+ break
+
+ ret = set()
+ for suggestedSkillName in suggestions:
+ speakableName = self._skillStoreData.get(suggestedSkillName, dict()).get('speakableName', '')
+
+ if not speakableName:
+ continue
+
+ ret.add((suggestedSkillName, speakableName))
+
+ return ret
+
+
def getSkillUpdateTag(self, skillName: str) -> str:
try:
return self._getSkillUpdateVersion(skillName)[1]
diff --git a/core/base/SuperManager.py b/core/base/SuperManager.py
index c1c990447..712747913 100644
--- a/core/base/SuperManager.py
+++ b/core/base/SuperManager.py
@@ -54,77 +54,87 @@ def __init__(self, mainClass):
def onStart(self):
- commons = self._managers.pop('CommonsManager')
- commons.onStart()
-
- configManager = self._managers.pop('ConfigManager')
- configManager.onStart()
-
- languageManager = self._managers.pop('LanguageManager')
- languageManager.onStart()
-
- locationManager = self._managers.pop('LocationManager')
- locationManager.onStart()
-
- deviceManager = self._managers.pop('DeviceManager')
- deviceManager.onStart()
-
- audioServer = self._managers.pop('AudioManager')
- audioServer.onStart()
-
- internetManager = self._managers.pop('InternetManager')
- internetManager.onStart()
-
- databaseManager = self._managers.pop('DatabaseManager')
- databaseManager.onStart()
-
- userManager = self._managers.pop('UserManager')
- userManager.onStart()
-
- mqttManager = self._managers.pop('MqttManager')
- mqttManager.onStart()
-
- talkManager = self._managers.pop('TalkManager')
- skillManager = self._managers.pop('SkillManager')
- assistantManager = self._managers.pop('AssistantManager')
- dialogTemplateManager = self._managers.pop('DialogTemplateManager')
- nluManager = self._managers.pop('NluManager')
- nodeRedManager = self._managers.pop('NodeRedManager')
-
- for manager in self._managers.values():
- if manager:
- manager.onStart()
-
- talkManager.onStart()
- nluManager.onStart()
- skillManager.onStart()
- dialogTemplateManager.onStart()
- assistantManager.onStart()
- nodeRedManager.onStart()
-
- self._managers[configManager.name] = configManager
- self._managers[audioServer.name] = audioServer
- self._managers[languageManager.name] = languageManager
- self._managers[locationManager.name] = locationManager
- self._managers[deviceManager.name] = deviceManager
- self._managers[talkManager.name] = talkManager
- self._managers[databaseManager.name] = databaseManager
- self._managers[userManager.name] = userManager
- self._managers[mqttManager.name] = mqttManager
- self._managers[skillManager.name] = skillManager
- self._managers[dialogTemplateManager.name] = dialogTemplateManager
- self._managers[assistantManager.name] = assistantManager
- self._managers[nluManager.name] = nluManager
- self._managers[internetManager.name] = internetManager
- self._managers[nodeRedManager.name] = nodeRedManager
+ try:
+ commons = self._managers.pop('CommonsManager')
+ commons.onStart()
+
+ configManager = self._managers.pop('ConfigManager')
+ configManager.onStart()
+
+ languageManager = self._managers.pop('LanguageManager')
+ languageManager.onStart()
+
+ locationManager = self._managers.pop('LocationManager')
+ locationManager.onStart()
+
+ deviceManager = self._managers.pop('DeviceManager')
+ deviceManager.onStart()
+
+ audioServer = self._managers.pop('AudioManager')
+ audioServer.onStart()
+
+ internetManager = self._managers.pop('InternetManager')
+ internetManager.onStart()
+
+ databaseManager = self._managers.pop('DatabaseManager')
+ databaseManager.onStart()
+
+ userManager = self._managers.pop('UserManager')
+ userManager.onStart()
+
+ mqttManager = self._managers.pop('MqttManager')
+ mqttManager.onStart()
+
+ talkManager = self._managers.pop('TalkManager')
+ skillManager = self._managers.pop('SkillManager')
+ assistantManager = self._managers.pop('AssistantManager')
+ dialogTemplateManager = self._managers.pop('DialogTemplateManager')
+ nluManager = self._managers.pop('NluManager')
+ nodeRedManager = self._managers.pop('NodeRedManager')
+
+ for manager in self._managers.values():
+ if manager:
+ manager.onStart()
+
+ talkManager.onStart()
+ nluManager.onStart()
+ skillManager.onStart()
+ dialogTemplateManager.onStart()
+ assistantManager.onStart()
+ nodeRedManager.onStart()
+
+ self._managers[configManager.name] = configManager
+ self._managers[audioServer.name] = audioServer
+ self._managers[languageManager.name] = languageManager
+ self._managers[locationManager.name] = locationManager
+ self._managers[deviceManager.name] = deviceManager
+ self._managers[talkManager.name] = talkManager
+ self._managers[databaseManager.name] = databaseManager
+ self._managers[userManager.name] = userManager
+ self._managers[mqttManager.name] = mqttManager
+ self._managers[skillManager.name] = skillManager
+ self._managers[dialogTemplateManager.name] = dialogTemplateManager
+ self._managers[assistantManager.name] = assistantManager
+ self._managers[nluManager.name] = nluManager
+ self._managers[internetManager.name] = internetManager
+ self._managers[nodeRedManager.name] = nodeRedManager
+ except Exception as e:
+ import traceback
+
+ traceback.print_exc()
+ Logger().logFatal(f'Error while starting managers: {e}')
def onBooted(self):
self.mqttManager.playSound(soundFilename='boot')
- for manager in self._managers.values():
- if manager:
- manager.onBooted()
+ manager = None
+ try:
+ for manager in self._managers.values():
+ if manager:
+ manager.onBooted()
+ except Exception as e:
+ Logger().logError(f'Error while sending onBooted to manager **{manager.name}**: {e}')
@staticmethod
@@ -197,8 +207,9 @@ def initManagers(self):
def onStop(self):
managerName = constants.UNKNOWN_MANAGER
- mqttManager = self._managers.pop('MqttManager')
try:
+ mqttManager = self._managers.pop('MqttManager')
+
for managerName, manager in self._managers.items():
manager.onStop()
@@ -206,8 +217,6 @@ def onStop(self):
mqttManager.onStop()
except Exception as e:
Logger().logError(f'Error while shutting down manager **{managerName}**: {e}')
- import traceback
- traceback.print_exc()
def getManager(self, managerName: str):
diff --git a/core/base/model/AliceSkill.py b/core/base/model/AliceSkill.py
index dde89e250..717b4f76e 100644
--- a/core/base/model/AliceSkill.py
+++ b/core/base/model/AliceSkill.py
@@ -59,8 +59,8 @@ def __init__(self, supportedIntents: Iterable = None, databaseSchema: dict = Non
self._widgets = dict()
self._deviceTypes = dict()
self._intentsDefinitions = dict()
- self._scenarioNodeName = ''
- self._scenarioNodeVersion = Version(mainVersion=0, updateVersion=0, hotfix=0)
+ self._scenarioPackageName = ''
+ self._scenarioPackageVersion = Version(mainVersion=0, updateVersion=0, hotfix=0)
self._supportedIntents: Dict[str, Intent] = self.buildIntentList(supportedIntents)
self.loadIntentsDefinition()
@@ -89,7 +89,7 @@ def addUtterance(self, text: str, intent: str) -> bool:
if not text in utterances:
utterances.append(text)
data['intents'][i]['utterances'] = utterances
- file.write_text(json.dumps(data, ensure_ascii=False, indent=4))
+ file.write_text(json.dumps(data, ensure_ascii=False, indent='\t'))
return True
return False
@@ -103,8 +103,8 @@ def loadScenarioNodes(self):
try:
with path.open('r') as fp:
data = json.load(fp)
- self._scenarioNodeName = data['name']
- self._scenarioNodeVersion = Version.fromString(data['version'])
+ self._scenarioPackageName = data['name']
+ self._scenarioPackageVersion = Version.fromString(data['version'])
except Exception as e:
self.logWarning(f'Failed to load scenario nodes: {e}')
@@ -402,12 +402,12 @@ def delayed(self, value: bool):
@property
def scenarioNodeName(self) -> str:
- return self._scenarioNodeName
+ return self._scenarioPackageName
@property
def scenarioNodeVersion(self) -> Version:
- return self._scenarioNodeVersion
+ return self._scenarioPackageVersion
@property
@@ -431,7 +431,7 @@ def instructions(self) -> str:
def hasScenarioNodes(self) -> bool:
- return self._scenarioNodeName != ''
+ return self._scenarioPackageName != ''
def subscribeIntents(self):
diff --git a/core/base/model/GithubCloner.py b/core/base/model/GithubCloner.py
index 4c4221b06..405d850ca 100644
--- a/core/base/model/GithubCloner.py
+++ b/core/base/model/GithubCloner.py
@@ -1,6 +1,5 @@
-from pathlib import Path
-
import shutil
+from pathlib import Path
from core.base.SuperManager import SuperManager
from core.base.model.ProjectAliceObject import ProjectAliceObject
@@ -40,12 +39,14 @@ def clone(self, skillName: str) -> bool:
def _doClone(self, skillName: str) -> bool:
try:
+ updateTag = self.SkillStoreManager.getSkillUpdateTag(skillName)
if not Path(self._dest / '.git').exists():
self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'init'])
self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'remote', 'add', 'origin', self._baseUrl])
+ self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'pull'])
- self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'pull', 'origin', 'master'])
- self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'checkout', self.SkillStoreManager.getSkillUpdateTag(skillName)])
+ self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'checkout', updateTag])
+ self.Commons.runSystemCommand(['git', '-C', str(self._dest), 'pull', 'origin', updateTag])
return True
except Exception as e:
diff --git a/core/base/model/Manager.py b/core/base/model/Manager.py
index f2bc47f8f..567476c34 100644
--- a/core/base/model/Manager.py
+++ b/core/base/model/Manager.py
@@ -41,11 +41,13 @@ def getFunctionCaller(self) -> Optional[str]:
def onStart(self):
self.logInfo(f'Starting')
+ self._isActive = True
return self._initDB()
def onStop(self):
self.logInfo(f'Stopping')
+ self._isActive = False
def _initDB(self):
diff --git a/core/base/model/ProjectAliceObject.py b/core/base/model/ProjectAliceObject.py
index ac7882c3b..b2cc42a8e 100644
--- a/core/base/model/ProjectAliceObject.py
+++ b/core/base/model/ProjectAliceObject.py
@@ -87,6 +87,8 @@ def broadcast(self, method: str, exceptions: list = None, manager = None, propag
for name in deadManagers:
del SM.SuperManager.getInstance().managers[name]
+ if method == 'onAudioFrame':
+ return
# Now send the event over mqtt
payload = dict()
@@ -98,7 +100,7 @@ def broadcast(self, method: str, exceptions: list = None, manager = None, propag
pass
self.MqttManager.publish(
- topic=method,
+ topic=f'projectalice/events/{method}',
payload=payload
)
@@ -750,3 +752,8 @@ def LocationManager(self): #NOSONAR
@property
def WakewordManager(self): #NOSONAR
return SM.SuperManager.getInstance().wakewordManager
+
+
+ @property
+ def NodeRedManager(self): #NOSONAR
+ return SM.SuperManager.getInstance().nodeRedManager
diff --git a/core/base/model/Widget.py b/core/base/model/Widget.py
index 210914802..d036a687f 100644
--- a/core/base/model/Widget.py
+++ b/core/base/model/Widget.py
@@ -77,7 +77,7 @@ def __init__(self, data: sqlite3.Row):
if 'zindex' in data.keys() and data['zindex'] is not None:
self._zindex = data['zindex']
else:
- self._zindex = 999
+ self._zindex = -1
updateWidget = True
if updateWidget:
diff --git a/core/commons/CommonsManager.py b/core/commons/CommonsManager.py
index 1f9f438a9..f22a94d4c 100644
--- a/core/commons/CommonsManager.py
+++ b/core/commons/CommonsManager.py
@@ -13,6 +13,7 @@
from datetime import datetime
from pathlib import Path
from typing import Any, Union
+from uuid import UUID
import requests
from googletrans import Translator
@@ -335,6 +336,15 @@ def randomNumber(self, length: int) -> int:
return int(number) if not number.startswith('0') else self.randomNumber(length)
+ @staticmethod
+ def isUuid(uuid: str) -> bool:
+ try:
+ _ = UUID(uuid)
+ return True
+ except ValueError:
+ return False
+
+
# noinspection PyUnusedLocal
def py_error_handler(filename, line, function, err, fmt): #NOSONAR
# Errors are handled by our loggers
diff --git a/core/commons/constants.py b/core/commons/constants.py
index c70c99acb..cb3bc1c02 100644
--- a/core/commons/constants.py
+++ b/core/commons/constants.py
@@ -1,4 +1,4 @@
-VERSION = '1.0.0-b3'
+VERSION = '1.0.0-b4'
DEFAULT = 'default'
UNKNOWN_WORD = 'unknownword'
@@ -15,6 +15,8 @@
GITHUB_RAW_URL = 'https://raw.githubusercontent.com/project-alice-assistant'
GITHUB_API_URL = 'https://api.github.com/repos/project-alice-assistant'
SKILL_REDIRECT_URL = 'https://skills.projectalice.ch'
+SKILLS_STORE_ASSETS = 'https://skills.projectalice.io/assets/store/skills.json'
+SKILLS_SAMPLES_STORE_ASSETS = 'https://skills.projectalice.io/assets/store/skills.samples'
GITHUB_REPOSITORY_ID = 193512918
JSON_EXT = '.json'
diff --git a/core/device/DeviceManager.py b/core/device/DeviceManager.py
index a8cf57016..16de50973 100644
--- a/core/device/DeviceManager.py
+++ b/core/device/DeviceManager.py
@@ -555,7 +555,7 @@ def getAliceTypeDevices(self, connectedOnly: bool = False, includeMain: bool = F
def getAliceTypeDeviceTypeIds(self):
- return [self.getMainDevice().deviceTypeID, self.getDeviceTypeByName(self.SAT_TYPE)]
+ return [self.getMainDevice().deviceTypeID, self.getDeviceTypeByName(self.SAT_TYPE).id]
def siteIdToDeviceName(self, siteId: str) -> str:
diff --git a/core/device/LocationManager.py b/core/device/LocationManager.py
index 843923806..fb157952b 100644
--- a/core/device/LocationManager.py
+++ b/core/device/LocationManager.py
@@ -63,6 +63,10 @@ def deleteLocation(self, locId: int) -> bool:
callerName=self.name,
values={'id': locId})
self._locations.pop(locId, None)
+
+ self.DatabaseManager.delete(tableName=self.DeviceManager.DB_LINKS,
+ callerName=self.DeviceManager.name,
+ values={'locationID': locId})
return True
diff --git a/core/dialog/DialogTemplateManager.py b/core/dialog/DialogTemplateManager.py
index efab84e8b..4b7afab20 100644
--- a/core/dialog/DialogTemplateManager.py
+++ b/core/dialog/DialogTemplateManager.py
@@ -165,7 +165,7 @@ def buildCache(self):
for file in pathToResources.glob(f'*{constants.JSON_EXT}'):
cached[skillName][file.stem] = self.Commons.fileChecksum(file)
- self._pathToChecksums.write_text(json.dumps(cached, indent=4, sort_keys=True))
+ self._pathToChecksums.write_text(json.dumps(cached, indent='\t', sort_keys=True))
def cleanCache(self, skillName: str):
@@ -176,7 +176,7 @@ def cleanCache(self, skillName: str):
checksums = json.load(self._pathToChecksums)
checksums.pop(skillName, None)
- self._pathToChecksums.write_text(json.dumps(checksums, indent=4, sort_keys=True))
+ self._pathToChecksums.write_text(json.dumps(checksums, indent='\t', sort_keys=True))
def clearCache(self, rebuild: bool = True):
@@ -193,7 +193,7 @@ def addUtterance(self, session: DialogSession):
if not text:
return
- intent = session.previousIntent
+ intent = session.secondLastIntent
if not intent:
return
diff --git a/core/dialog/model/DialogSession.py b/core/dialog/model/DialogSession.py
index 9cb7e3487..a59da28c2 100644
--- a/core/dialog/model/DialogSession.py
+++ b/core/dialog/model/DialogSession.py
@@ -1,7 +1,7 @@
from __future__ import annotations
from dataclasses import dataclass, field
-from typing import Any
+from typing import Any, Optional
from paho.mqtt.client import MQTTMessage
@@ -101,5 +101,16 @@ def addToHistory(self, intent: Intent):
@property
- def previousIntent(self) -> str:
- return str(self.intentHistory[-1])
+ def previousIntent(self) -> Optional[str]:
+ try:
+ return str(self.intentHistory[-1])
+ except:
+ return None
+
+
+ @property
+ def secondLastIntent(self) -> Optional[str]:
+ try:
+ return str(self.intentHistory[-2])
+ except:
+ return None
diff --git a/core/interface/NodeRedManager.py b/core/interface/NodeRedManager.py
index 9e411fa6c..2fec477ae 100644
--- a/core/interface/NodeRedManager.py
+++ b/core/interface/NodeRedManager.py
@@ -1,25 +1,113 @@
import json
+import os
+import shutil
+import time
from pathlib import Path
+from subprocess import PIPE, Popen
from core.base.model.Manager import Manager
-from core.base.model.Version import Version
class NodeRedManager(Manager):
+ PACKAGE_PATH = Path('../.node-red/package.json')
+ DEFAULT_NODES_ACTIVE = {
+ 'node-red': [
+ 'debug',
+ 'JSON',
+ 'split',
+ 'sort',
+ 'function',
+ 'change'
+ ]
+ }
def __init__(self):
super().__init__()
def onStart(self):
+ self.isActive = self.ConfigManager.getAliceConfigByName('scenariosActive')
+
+ if not self.isActive:
+ return
+
super().onStart()
+
+ if not self.PACKAGE_PATH.exists():
+ self.isActive = False
+ self.ThreadManager.newThread(name='installNodered', target=self.install)
+ return
+
self.injectSkillNodes()
self.Commons.runRootSystemCommand(['systemctl', 'start', 'nodered'])
+ def install(self):
+ self.logInfo('Node-RED not found, installing, this might take a while...')
+ self.Commons.downloadFile(
+ url='https://raw.githubusercontent.com/node-red/linux-installers/master/deb/update-nodejs-and-nodered',
+ dest='var/cache/node-red.sh'
+ )
+ self.Commons.runRootSystemCommand('chmod +x var/cache/node-red.sh'.split())
+
+ process = Popen('./var/cache/node-red.sh'.split(), stdin=PIPE, stdout=PIPE)
+ try:
+ process.stdin.write(b'y\n')
+ process.stdin.write(b'n\n')
+ except IOError:
+ self.logError('Failed installing Node-RED')
+ self.onStop()
+ return
+
+ process.stdin.close()
+ returnCode = process.wait(timeout=900)
+
+ if returnCode:
+ self.logError('Failed installing Node-red')
+ self.onStop()
+ else:
+ self.logInfo('Succesfully installed Node-red')
+ self.configureNewNodeRed()
+ self.onStart()
+
+
+ def configureNewNodeRed(self):
+ self.logInfo('Configuring')
+ # Start to generate base configs and stop it after
+ self.Commons.runRootSystemCommand(['systemctl', 'start', 'nodered'])
+ time.sleep(5)
+ self.Commons.runRootSystemCommand(['systemctl', 'stop', 'nodered'])
+ time.sleep(3)
+
+ config = Path(self.PACKAGE_PATH.parent, '.config.nodes.json')
+ data = json.loads(config.read_text())
+ for package in data.values():
+ keeper = self.DEFAULT_NODES_ACTIVE.get(package['name'], list())
+ for node in package['nodes'].values():
+ if node['name'] in keeper:
+ continue
+ node['enabled'] = False
+
+ config.write_text(json.dumps(data))
+ self.logInfo('Nodes configured')
+ self.logInfo('Applying Project Alice settings')
+
+ self.Commons.runSystemCommand('npm install --prefix ~/.node-red @node-red-contrib-themes/midnight-red'.split())
+ shutil.copy(Path('system/node-red/settings.js'), Path(os.path.expanduser('~/.node-red'), 'settings.js'))
+ self.logInfo("All done, let's start all this")
+
+
def onStop(self):
super().onStop()
- self.Commons.runRootSystemCommand(['systemctl', 'stop', 'nodered'])
+ if not self.ConfigManager.getAliceConfigByName('dontStopNodeRed'):
+ self.Commons.runRootSystemCommand(['systemctl', 'stop', 'nodered'])
+
+
+ def toggle(self):
+ if self.isActive:
+ self.onStop()
+ else:
+ self.onStart()
def reloadServer(self):
@@ -27,28 +115,43 @@ def reloadServer(self):
def injectSkillNodes(self):
- package = Path('../.node-red/package.json')
- if not package.exists():
- #self.logWarning('Package json file for Node Red is missing. Is Node Red even installed?')
+ restart = False
+
+ if not self.PACKAGE_PATH.exists():
+ self.logWarning('Package json file for Node-RED is missing. Is Node-RED even installed?')
+ self.onStop()
return
for skillName, tup in self.SkillManager.allScenarioNodes().items():
scenarioNodeName, scenarioNodeVersion, scenarioNodePath = tup
path = Path('../.node-red/node_modules', scenarioNodeName, 'package.json')
if not path.exists():
- self.logInfo('New scenario node found')
+ self.logInfo(f'New scenario node found for skill **{skillName}**: {scenarioNodeName}')
install = self.Commons.runSystemCommand(f'cd ~/.node-red && npm install {scenarioNodePath}', shell=True)
if install.returncode == 1:
- self.logWarning(f'Something went wrong installing new node: {install.stderr}')
-
+ self.logWarning(f'Something went wrong installing new node **{scenarioNodeName}**: {install.stderr}')
+ else:
+ restart = True
continue
- with path.open('r') as fp:
- data = json.load(fp)
- version = Version.fromString(data['version'])
+ version = self.SkillManager.getSkillScenarioVersion(skillName)
- if version < scenarioNodeVersion:
- self.logInfo('New scenario node update found')
- install = self.Commons.runSystemCommand(f'cd ~/.node-red && npm install {scenarioNodePath}', shell=True)
- if install.returncode == 1:
- self.logWarning(f'Something went wrong updating node: {install.stderr}')
+ if version < scenarioNodeVersion:
+ self.logInfo(f'New scenario update found for node **{scenarioNodeName}**')
+ install = self.Commons.runSystemCommand(f'cd ~/.node-red && npm install {scenarioNodePath}', shell=True)
+ if install.returncode == 1:
+ self.logWarning(f'Something went wrong updating node **{scenarioNodeName}**: {install.stderr}')
+ continue
+
+ self.DatabaseManager.update(
+ tableName='skills',
+ callerName=self.SkillManager.name,
+ values={
+ 'scenarioVersion': str(scenarioNodeVersion)
+ },
+ row=('skillName', skillName)
+ )
+ restart = True
+
+ if restart:
+ self.reloadServer()
diff --git a/core/interface/WebInterfaceManager.py b/core/interface/WebInterfaceManager.py
index dfd37122f..c66b82fba 100644
--- a/core/interface/WebInterfaceManager.py
+++ b/core/interface/WebInterfaceManager.py
@@ -119,7 +119,7 @@ def onStart(self):
# 'ssl_context' : 'adhoc',
'debug' : self.ConfigManager.getAliceConfigByName('debug'),
'port' : int(self.ConfigManager.getAliceConfigByName('webInterfacePort')),
- 'host' : self.Commons.getLocalIp(),
+ 'host' : '0.0.0.0',
'use_reloader': False
}
)
diff --git a/core/interface/languages/de.json b/core/interface/languages/de.json
index 1b19a0bbe..7390806c7 100644
--- a/core/interface/languages/de.json
+++ b/core/interface/languages/de.json
@@ -15,46 +15,47 @@
"updatee": "aktualisieren",
"addWidgetTitle": "Verfügbare Widgets",
"viewIntents": "Intents ansehen",
- "skillSettings": "Skilleinstellungen",
- "save": "Speichern",
- "followLogs": "Autoscroll",
- "adminTabTitleSetting": "Alice Einstellungen",
- "adminTabTitleUtilities": "Utilities",
- "restartAlice": "Alice neustarten",
- "reboot": "Reboot",
- "loading": "Laden...",
- "trainAssistant": "Assistant trainieren",
- "wipeAll": "Alles löschen",
- "devmodeNewSkill": "Neuer Skill",
- "devmodeEditSkill": "Skill editieren",
- "reload": "Neu laden",
- "skillName": "Skill Name",
- "skillDesc": "Skill Beschreibung",
- "skillLanguage": "Skill Sprache",
- "english": "Englisch",
- "french": "Französisch",
- "german": "Deutsch",
- "italian": "Italienisch",
- "pipreq": "PIP Anforderungen",
- "sysreq": "System Anforderungen",
- "conditions": "Bedingungen",
- "conditionOnline": "Benötigt eine bestehende Internetverbindung",
- "conditionSkill": "Benötigt weitere Skills",
- "conditionNotSkill": "Skills die nicht parallel installiert sein dürfen",
- "conditionASRArbitrary": "Dieser Skill benötigt Wörter, auf welche die Spracherkennung nicht trainiert wurde",
- "conditionActiveManager": "Die gelisteten Manager werden benötigt ",
- "widgets": "Widgets",
+ "skillSettings" : "Skilleinstellungen",
+ "save" : "Speichern",
+ "followLogs" : "Autoscroll",
+ "adminTabTitleSetting" : "Alice Einstellungen",
+ "adminTabTitleUtilities" : "Utilities",
+ "restartAlice" : "Alice neustarten",
+ "reboot" : "Reboot",
+ "loading" : "Laden...",
+ "trainAssistant" : "Assistant trainieren",
+ "wipeAll" : "Alles löschen",
+ "devmodeNewSkill" : "Neuer Skill",
+ "devmodeEditSkill" : "Skill editieren",
+ "reload" : "Neu laden",
+ "skillName" : "Skill Name",
+ "skillDesc" : "Skill Beschreibung",
+ "skillLanguage" : "Skill Sprache",
+ "english" : "Englisch",
+ "french" : "Französisch",
+ "german" : "Deutsch",
+ "italian" : "Italienisch",
+ "polish" : "Polish",
+ "pipreq" : "PIP Anforderungen",
+ "sysreq" : "System Anforderungen",
+ "conditions" : "Bedingungen",
+ "conditionOnline" : "Benötigt eine bestehende Internetverbindung",
+ "conditionSkill" : "Benötigt weitere Skills",
+ "conditionNotSkill" : "Skills die nicht parallel installiert sein dürfen",
+ "conditionASRArbitrary" : "Dieser Skill benötigt Wörter, auf welche die Spracherkennung nicht trainiert wurde",
+ "conditionActiveManager" : "Die gelisteten Manager werden benötigt ",
+ "widgets" : "Widgets",
"commaSeparatedExplanation": "Einzelne Elemente mit einem Komma trennen, zum Beispiel 'foo, bar, baz'",
- "createSkill" : "Erstelle den Skill",
- "uploadSkillToGithub" : "Den Skill auf Github hochladen",
- "resetSkill" : "Reset",
- "actions" : "Actions",
- "addUser" : "Neuer Benutzer",
- "addWakeword" : "Neues Wakeword",
- "tuneWakeword" : "Tune wakeword",
- "widgetCustStyleTitle" : "Anzeige Einstellungen",
- "widgetConfigTitle" : "Widget Einstellungen",
- "noConfs" : "Keine Einstellungen",
+ "createSkill" : "Erstelle den Skill",
+ "uploadSkillToGithub" : "Den Skill auf Github hochladen",
+ "resetSkill" : "Reset",
+ "actions" : "Actions",
+ "addUser" : "Neuer Benutzer",
+ "addWakeword" : "Neues Wakeword",
+ "tuneWakeword" : "Tune wakeword",
+ "widgetCustStyleTitle" : "Anzeige Einstellungen",
+ "widgetConfigTitle" : "Widget Einstellungen",
+ "noConfs" : "Keine Einstellungen",
"saving" : "Speichern",
"saved" : "Gespeichert",
"saveFailed" : "Speichern fehlgeschlagen",
@@ -68,5 +69,9 @@
"renameLocation" : "Wie lautet der neue Name der Lokation?",
"warningSkillChangeAliceconf": "Änderung der Systemeinstellungen durch Skills erkannt",
"accept" : "akzeptieren",
- "refuse" : "ablehnen"
+ "refuse" : "ablehnen",
+ "leaveEmptyIfNotNeeded" : "Leave empty is not needed",
+ "scenarioNodes" : "Scenario nodes",
+ "category" : "Category",
+ "speakableName" : "Speakable name"
}
diff --git a/core/interface/languages/en.json b/core/interface/languages/en.json
index 480dc6b1c..5a8c65a93 100644
--- a/core/interface/languages/en.json
+++ b/core/interface/languages/en.json
@@ -15,46 +15,47 @@
"updatee": "Update",
"addWidgetTitle": "Available widgets",
"viewIntents": "View intents",
- "skillSettings": "Skill settings",
- "save": "Save",
- "followLogs": "Autoscroll",
- "adminTabTitleSetting": "Alice Settings",
- "adminTabTitleUtilities": "Utilities",
- "restartAlice": "Restart Alice",
- "reboot": "Reboot",
- "trainAssistant": "Train assistant",
- "wipeAll": "Wipe all",
- "devmodeNewSkill": "Skill creation",
- "devmodeEditSkill": "Skill edition",
- "reload": "Reload",
- "loading": "Loading...",
- "skillName": "Skill name",
- "skillDesc": "Skill description",
- "skillLanguage": "Skill language",
- "english": "English",
- "french": "French",
- "german": "German",
- "italian": "Italian",
- "pipreq": "PIP requirements",
- "sysreq": "System requirements",
- "conditions": "Conditions",
- "conditionOnline": "Requires to be connected to the internet",
- "conditionSkill": "Required skills",
- "conditionNotSkill": "Skills that can't be installed at the same time",
- "conditionASRArbitrary": "This skill requires the ASR to understand words that were not trained",
- "conditionActiveManager": "Managers listed are required to be running",
- "widgets": "Widgets",
+ "skillSettings" : "Skill settings",
+ "save" : "Save",
+ "followLogs" : "Autoscroll",
+ "adminTabTitleSetting" : "Alice Settings",
+ "adminTabTitleUtilities" : "Utilities",
+ "restartAlice" : "Restart Alice",
+ "reboot" : "Reboot",
+ "trainAssistant" : "Train assistant",
+ "wipeAll" : "Wipe all",
+ "devmodeNewSkill" : "Skill creation",
+ "devmodeEditSkill" : "Skill edition",
+ "reload" : "Reload",
+ "loading" : "Loading...",
+ "skillName" : "Skill name",
+ "skillDesc" : "Skill description",
+ "skillLanguage" : "Skill language",
+ "english" : "English",
+ "french" : "French",
+ "german" : "German",
+ "italian" : "Italian",
+ "polish" : "Polish",
+ "pipreq" : "PIP requirements",
+ "sysreq" : "System requirements",
+ "conditions" : "Conditions",
+ "conditionOnline" : "Requires to be connected to the internet",
+ "conditionSkill" : "Required skills",
+ "conditionNotSkill" : "Skills that can't be installed at the same time",
+ "conditionASRArbitrary" : "This skill requires the ASR to understand words that were not trained",
+ "conditionActiveManager" : "Managers listed are required to be running",
+ "widgets" : "Widgets",
"commaSeparatedExplanation": "Enter a list separated by comma, example 'foo, bar, baz'",
- "createSkill" : "Create the skill",
- "uploadSkillToGithub" : "Upload the skill to Github",
- "resetSkill" : "Reset",
- "actions" : "Actions",
- "addUser" : "Add user",
- "addWakeword" : "Add wakeword",
- "tuneWakeword" : "Tune wakeword",
- "widgetCustStyleTitle" : "Display Options",
- "widgetConfigTitle" : "Widget Options",
- "noConfs" : "No config for this widget!",
+ "createSkill" : "Create the skill",
+ "uploadSkillToGithub" : "Upload the skill to Github",
+ "resetSkill" : "Reset",
+ "actions" : "Actions",
+ "addUser" : "Add user",
+ "addWakeword" : "Add wakeword",
+ "tuneWakeword" : "Tune wakeword",
+ "widgetCustStyleTitle" : "Display Options",
+ "widgetConfigTitle" : "Widget Options",
+ "noConfs" : "No config for this widget!",
"saving" : "Saving",
"saved" : "Saved",
"saveFailed" : "Save failed",
@@ -68,5 +69,9 @@
"renameLocation" : "What is the locations new name?",
"warningSkillChangeAliceconf": "Core configuration change attempt detected!",
"accept" : "accept",
- "refuse" : "refuse"
+ "refuse" : "refuse",
+ "leaveEmptyIfNotNeeded" : "Leave empty is not needed",
+ "scenarioNodes" : "Scenario nodes",
+ "category" : "Category",
+ "speakableName" : "Speakable name"
}
diff --git a/core/interface/languages/fr.json b/core/interface/languages/fr.json
index 5f98f7962..3722d4340 100644
--- a/core/interface/languages/fr.json
+++ b/core/interface/languages/fr.json
@@ -15,46 +15,47 @@
"updatee": "mise à jour",
"addWidgetTitle": "Widgets disponibles",
"viewIntents": "Voir intents",
- "skillSettings": "Configuration",
- "save": "Sauvegarder",
- "followLogs": "Autoscroll",
- "adminTabTitleSetting": "Parametres Alice",
- "adminTabTitleUtilities": "Utilitaires",
- "restartAlice": "Relancer Alice",
- "reboot": "Reboot",
- "trainAssistant": "Entrainer assistant",
- "wipeAll": "Tout supprimer",
- "devmodeNewSkill": "Nouveau skill",
- "devmodeEditSkill": "Edition de skill",
- "reload": "Recharger",
- "loading": "Chargement...",
- "skillName": "Nom du skill",
- "skillDesc": "Description du skill",
- "skillLanguage": "Langue du skill",
- "english": "Anglais",
- "french": "Français",
- "german": "Allemand",
- "italian": "Italien",
- "pipreq": "Dépendances PIP",
- "sysreq": "Dépendance systeme",
- "conditions": "Conditions",
- "conditionOnline": "Connexion à internet requise",
- "conditionSkill": "Skills requis",
- "conditionNotSkill": "Skills qui ne doivent pas être installé en même temps",
- "conditionASRArbitrary": "Ce skill a besoin d'un ASR qui comprend des mots pour lesquels il n'a pas été entrainé",
- "conditionActiveManager": "Managers qui doivent être actifs pour que ce skill fonctionne",
- "widgets": "Widgets",
+ "skillSettings" : "Configuration",
+ "save" : "Sauvegarder",
+ "followLogs" : "Autoscroll",
+ "adminTabTitleSetting" : "Parametres Alice",
+ "adminTabTitleUtilities" : "Utilitaires",
+ "restartAlice" : "Relancer Alice",
+ "reboot" : "Reboot",
+ "trainAssistant" : "Entrainer assistant",
+ "wipeAll" : "Tout supprimer",
+ "devmodeNewSkill" : "Nouveau skill",
+ "devmodeEditSkill" : "Edition de skill",
+ "reload" : "Recharger",
+ "loading" : "Chargement...",
+ "skillName" : "Nom du skill",
+ "skillDesc" : "Description du skill",
+ "skillLanguage" : "Langue du skill",
+ "english" : "Anglais",
+ "french" : "Français",
+ "german" : "Allemand",
+ "italian" : "Italien",
+ "polish" : "Polonais",
+ "pipreq" : "Dépendances PIP",
+ "sysreq" : "Dépendance systeme",
+ "conditions" : "Conditions",
+ "conditionOnline" : "Connexion à internet requise",
+ "conditionSkill" : "Skills requis",
+ "conditionNotSkill" : "Skills qui ne doivent pas être installé en même temps",
+ "conditionASRArbitrary" : "Ce skill a besoin d'un ASR qui comprend des mots pour lesquels il n'a pas été entrainé",
+ "conditionActiveManager" : "Managers qui doivent être actifs pour que ce skill fonctionne",
+ "widgets" : "Widgets",
"commaSeparatedExplanation": "Une liste séparée par des virgules, exemple 'foo, bar, baz'",
- "createSkill" : "Créer le skill",
- "uploadSkillToGithub" : "Uploader le skill sur Github",
- "resetSkill" : "Reset",
- "actions" : "Actions",
- "addUser" : "Ajouter utilisateur",
- "addWakeword" : "Ajouter wakeword",
- "tuneWakeword" : "Tuner wakeword",
- "widgetCustStyleTitle" : "Options d'affichage",
- "widgetConfigTitle" : "Options de widget",
- "noConfs" : "Pas de config pour ce widget!",
+ "createSkill" : "Créer le skill",
+ "uploadSkillToGithub" : "Uploader le skill sur Github",
+ "resetSkill" : "Reset",
+ "actions" : "Actions",
+ "addUser" : "Ajouter utilisateur",
+ "addWakeword" : "Ajouter wakeword",
+ "tuneWakeword" : "Tuner wakeword",
+ "widgetCustStyleTitle" : "Options d'affichage",
+ "widgetConfigTitle" : "Options de widget",
+ "noConfs" : "Pas de config pour ce widget!",
"saving" : "Sauvegarde",
"saved" : "Sauvegardé",
"saveFailed" : "Erreur de sauvegarde",
@@ -68,5 +69,9 @@
"renameLocation" : "Quel est le nouveau nom de l'emplacement?",
"warningSkillChangeAliceconf": "Modification de configuration de base demandée par un ou des skill(s)",
"accept" : "accepter",
- "refuse" : "refuser"
+ "refuse" : "refuser",
+ "leaveEmptyIfNotNeeded" : "Leave empty is not needed",
+ "scenarioNodes" : "Scenario nodes",
+ "category" : "Category",
+ "speakableName" : "Nom prononçable"
}
diff --git a/core/interface/languages/it.json b/core/interface/languages/it.json
index f2c172fb2..230ff3fb0 100644
--- a/core/interface/languages/it.json
+++ b/core/interface/languages/it.json
@@ -15,46 +15,47 @@
"updatee": "aggiorna",
"addWidgetTitle": "Widgets disponibili",
"viewIntents": "Visualizza intents",
- "skillSettings": "Impostazioni skills",
- "save": "Save",
- "followLogs": "Autoscroll",
- "adminTabTitleSetting": "Impostazioni Alice",
- "adminTabTitleUtilities": "Utilities",
- "restartAlice": "Riavvia Alice",
- "reboot": "Riavvia il Sistema",
- "trainAndDlAssistant": "Scarica Assistente",
- "wipeAll": "Cancella tutto",
- "devmodeNewSkill": "Crea skill",
- "devmodeEditSkill": "Modifica skill",
- "reload": "Ricarica",
- "loading": "Caricamento...",
- "skillName": "nome Skill",
- "skillDesc": "descrizione skill",
- "skillLanguage": "Lingua skill",
- "english": "Inglese",
- "french": "Francese",
- "german": "Tedesco",
- "italian": "Italiano",
- "pipreq": "Prerequisiti di PIP",
- "sysreq": "Prerequisiti di sistema",
- "conditions": "Condizioni",
- "conditionOnline": "Richiede una connessione ad internet",
- "conditionSkill": "Skills richieste",
- "conditionNotSkill": "Skills che non possono essere installate contemporaneamente",
- "conditionASRArbitrary": "Questa skill contiene parole che l'ASR non è addestrato a riconoscere",
- "conditionActiveManager": "Devono essere avviati i seguenti gestori",
- "widgets": "Widgets",
+ "skillSettings" : "Impostazioni skills",
+ "save" : "Save",
+ "followLogs" : "Autoscroll",
+ "adminTabTitleSetting" : "Impostazioni Alice",
+ "adminTabTitleUtilities" : "Utilities",
+ "restartAlice" : "Riavvia Alice",
+ "reboot" : "Riavvia il Sistema",
+ "trainAndDlAssistant" : "Scarica Assistente",
+ "wipeAll" : "Cancella tutto",
+ "devmodeNewSkill" : "Crea skill",
+ "devmodeEditSkill" : "Modifica skill",
+ "reload" : "Ricarica",
+ "loading" : "Caricamento...",
+ "skillName" : "nome Skill",
+ "skillDesc" : "descrizione skill",
+ "skillLanguage" : "Lingua skill",
+ "english" : "Inglese",
+ "french" : "Francese",
+ "german" : "Tedesco",
+ "italian" : "Italiano",
+ "polish" : "Polish",
+ "pipreq" : "Prerequisiti di PIP",
+ "sysreq" : "Prerequisiti di sistema",
+ "conditions" : "Condizioni",
+ "conditionOnline" : "Richiede una connessione ad internet",
+ "conditionSkill" : "Skills richieste",
+ "conditionNotSkill" : "Skills che non possono essere installate contemporaneamente",
+ "conditionASRArbitrary" : "Questa skill contiene parole che l'ASR non è addestrato a riconoscere",
+ "conditionActiveManager" : "Devono essere avviati i seguenti gestori",
+ "widgets" : "Widgets",
"commaSeparatedExplanation": "Una lista separata da virgole, per esempio 'foo, bar, baz'",
- "createSkill" : "Crea la skill",
- "uploadSkillToGithub" : "Carica la skill su Github",
- "resetSkill" : "Resetta",
- "actions" : "Azioni",
- "addUser" : "Nuovo utente",
- "addWakeword" : "Aggiungi wakeword",
- "tuneWakeword" : "Regola wakeword",
- "widgetCustStyleTitle" : "Opzioni dello schermo",
- "widgetConfigTitle" : "Opzioni dei widget",
- "noConfs" : "Nessuna configurazione per questo widget!",
+ "createSkill" : "Crea la skill",
+ "uploadSkillToGithub" : "Carica la skill su Github",
+ "resetSkill" : "Resetta",
+ "actions" : "Azioni",
+ "addUser" : "Nuovo utente",
+ "addWakeword" : "Aggiungi wakeword",
+ "tuneWakeword" : "Regola wakeword",
+ "widgetCustStyleTitle" : "Opzioni dello schermo",
+ "widgetConfigTitle" : "Opzioni dei widget",
+ "noConfs" : "Nessuna configurazione per questo widget!",
"saving" : "Salvataggio in corso",
"saved" : "Salvato",
"saveFailed" : "Salvataggio fallito",
@@ -68,5 +69,9 @@
"renameLocation" : "Qual è il nuovo nome per la posizione?",
"warningSkillChangeAliceconf": "Some skill(s) want to change some core configurations",
"accept" : "accettare",
- "refuse" : "refiutare"
+ "refuse" : "refiutare",
+ "leaveEmptyIfNotNeeded" : "Leave empty is not needed",
+ "scenarioNodes" : "Scenario nodes",
+ "category" : "Category",
+ "speakableName" : "Speakable name"
}
diff --git a/core/interface/languages/pl.json b/core/interface/languages/pl.json
new file mode 100644
index 000000000..3f3052f3f
--- /dev/null
+++ b/core/interface/languages/pl.json
@@ -0,0 +1,73 @@
+{
+ "home": "start",
+ "skills": "umiejętności",
+ "myHome": "mój dom",
+ "scenarios": "scenariusze",
+ "syslog": "log systemowy",
+ "alicewatch": "alicewatch",
+ "admin": "admin",
+ "devMode": "tryb dev",
+ "by": "przez",
+ "status": "status",
+ "enable": "włącz",
+ "disable": "wyłącz",
+ "delete": "usuń",
+ "updatee": "Aktualizuj",
+ "addWidgetTitle": "Dostępne widżety",
+ "viewIntents": "Zobacz intencje",
+ "skillSettings": "Skill settings",
+ "save": "Zapisz",
+ "followLogs": "Auto-przewijanie",
+ "adminTabTitleSetting": "Ustawienia",
+ "adminTabTitleUtilities": "Narzędzia",
+ "restartAlice": "Uruchom ponownie Alice",
+ "reboot": "Uruchom ponownie",
+ "trainAssistant": "Trenuj asystenta",
+ "wipeAll": "Usuń wszystko",
+ "devmodeNewSkill": "Utwórz umiejętność",
+ "devmodeEditSkill": "Edycja umijejętności",
+ "reload": "Przeładuj",
+ "loading": "Ładowanie...",
+ "skillName": "Nazwa umiejętności",
+ "skillDesc": "Opis umiejętności",
+ "skillLanguage": "Język umiejętności",
+ "english": "Angielski",
+ "french": "Francuski",
+ "german": "Niemiecki",
+ "italian": "Włoski",
+ "polish": "Polski",
+ "pipreq": "Wymagania PIP",
+ "sysreq": "Wymagania systempwe",
+ "conditions": "Warunki",
+ "conditionOnline": "Wymaga połączenia z internetem",
+ "conditionSkill": "Wymagane umiejętności",
+ "conditionNotSkill": "Umiejętności, które nie moga być instalowane w tym samym czasie",
+ "conditionASRArbitrary": "Ta umiejętność wymaga od modułu ASR zrozumienia słów, które nie były trenowane",
+ "conditionActiveManager": "Menedżerowie z listy poniżej muszą być uruchomieni",
+ "widgets": "Widżety",
+ "commaSeparatedExplanation": "Wprowadź listę rozdzieloną przecinkami, np. 'jeden, dwa, trzy'",
+ "createSkill" : "Utwórz umiejętność",
+ "uploadSkillToGithub" : "Wyślij umiejętność na Github",
+ "resetSkill" : "Reset",
+ "actions" : "Akcje",
+ "addUser" : "Dodaj użytkownika",
+ "addWakeword" : "Dodaj wyrażenie wywołujące",
+ "tuneWakeword" : "Popraw wyrażenie wywołujące",
+ "widgetCustStyleTitle" : "Wyświetl opcje",
+ "widgetConfigTitle" : "Opcje widżetu",
+ "noConfs" : "Brak konfiguracji widżetu!",
+ "saving" : "Zapisywanie",
+ "saved" : "Zapisano",
+ "saveFailed" : "Błąd zapisu",
+ "confMoveDevice" : "Czy chcesz umieścić to urządzenie w nowej lokalizacji: ",
+ "confDeleteDevice" : "Czy na pewno chcesz usunąć to urządzenie?",
+ "confDeleteZone" : "Czy na pewno chcesz usunąć tę lokalizację?",
+ "nameNewZone" : "Podaj nazwę lokalizacji",
+ "unreachable" : "Projekt Alice jest obecnie niedostępny,\nstrona spróbuje się wkrótce przeładować",
+ "instructions" : "Instrukcja",
+ "renameDevice" : "Podaj nową nazwę urządzenia?",
+ "renameLocation" : "Podaj nową nazwę lokalizacji?",
+ "warningSkillChangeAliceconf": "Wykryto próbę zmiany podstawowej konfiguracji!",
+ "accept" : "zaakceptuj",
+ "refuse" : "odmów"
+}
diff --git a/core/interface/static/css/myHome.css b/core/interface/static/css/myHome.css
index dea613d47..366a03a9b 100644
--- a/core/interface/static/css/myHome.css
+++ b/core/interface/static/css/myHome.css
@@ -1,4 +1,5 @@
-.floorPlan-Zone { overflow: visible; background-color: var(--mainBG); color: var(--windowBG); position: absolute; display: flex; justify-content: center; align-items: center; font-size: 1.5em; }
+.floorPlan-Zone { overflow: visible; background-color: var(--mainBG); color: var(--windowBG); position: absolute; display: flex; justify-content: center; align-items: center;}
+.floorPlan-Zone > .inputOrText {font-size: 1.5em;}
.floorPlan-Wall { background-color: var(--accent); }
diff --git a/core/interface/static/css/projectalice.css b/core/interface/static/css/projectalice.css
index 21d66942b..f2eef6c3a 100644
--- a/core/interface/static/css/projectalice.css
+++ b/core/interface/static/css/projectalice.css
@@ -94,26 +94,36 @@ button.button{ font-size: 1.6em; height: 2em; }
.pageTitle { font-size: 2em; margin-left: 1em; align-items: center; display: flex; width: 7.4em;}
.aliceStatus { flex-grow: 1; }
+
.updateChannelMarker { font-style: italic; margin: 0 .9em; align-items: center; display: flex; }
.resourceUsage { margin: 0 .9em; align-items: center; display: flex; }
-.page { flex: 1 100%; flex-flow: row nowrap; display: flex; }
+.page { flex: 1 100%; flex-flow: row nowrap; display: flex; }
.toolbar { background: var(--mainBG); display: flex; float: right; clear: both; padding-top: .2em; padding-left: 1em; border: solid 2px; border-bottom-left-radius: 1.5em; border-top: none; border-color: var(--accent); }
+
.toolbarButton { font-size: 2em; padding-right: .5em; min-width: .9em; text-align: center; }
-.zindexer { position: absolute; top: 15px; right: 15px; color: var(--accent); font-weight: bold; font-size: 1.3em; z-index: 999; cursor: pointer; }
-[class^='zindexer-']:hover { color: var(--hover); }
+.zindexer { position: absolute; top: 5px; left: 5px; background-color: var(--mainBG); width: 36px; border-radius: 18px; text-align: center; color: var(--text); z-index: 99; }
+
+.zindexer .clickable:first-child { margin-bottom: 5px; }
+
+.clickable { cursor: pointer; }
+
+.clickable:hover { color: var(--hover); }
/* Utility screen for administration */
.utility { width: 12em; height: 12em; flex-flow: column; align-items: center; }
+
.utilityIcon { text-align: center; font-size: 5em; margin-top: .4em; width: 100%; }
+
.utilityText { font-size: 1.25em; margin: .3em; text-align: center; width: 90%; }
/* console and logging */
/* using color markings as defined above */
.console { position: absolute; top: 20px; bottom: 40px; left: 20px; right: 20px; padding: 1em; background-color: black; color: var(--text); overflow-y: scroll; overflow-x: hidden; font-family: var(--readable); -moz-user-select: text; -webkit-touch-callout: default; -webkit-user-select: text; -ms-user-select: text; user-select: text; -khtml-user-select: text; }
+
.log { display: inline-block; }
.logLine { display: flex; font-family: var(--readable); font-size: 0.9em; margin: 2px 0 2px 0; align-items: flex-start; }
@@ -127,7 +137,8 @@ button.button{ font-size: 1.6em; height: 2em; }
.addWidgetCheck { float: right; color: var(--text); margin-right: .4em; }
/* To use with custom Widgets! */
-.widget { display: block; box-shadow: 0 0 20px 5px #141414; background-color: var(--windowBG); cursor: default; overflow: hidden; }
+.widget { display: block; box-shadow: 0 0 20px 5px #141414; background-color: var(--windowBG); cursor: default; overflow: hidden; font-family: var(--readable) }
+
.widgetIcons { font-size: 1.5em; display: flex; align-items: flex-start; }
.widgetIconsHidden .widgetIcons { display: none; }
.widgetIcon { display: flex; flex-grow: 1; }
@@ -140,33 +151,41 @@ button.button{ font-size: 1.6em; height: 2em; }
.tabsContainer { padding-bottom: .1em; border: 2px solid var(--secondary); }
.tabsContainer ul { display: flex; background-color: var(--secondary); margin-top: 0; padding-left: .4em; }
.tabsContainer li { background-color: var(--windowBG); font-size: 1.25em; padding: .2em .7em; margin: .3em .3em 0 0; display: inline-flex; align-items: center; box-sizing: border-box; border-radius: .3em .3em 0 0; border: 2px solid var(--accent); border-bottom: 0; }
-.tabsContainer .activeTab { background-color: var(--mainBG); }
-.tabsContent { display: flex; overflow-y: auto; overflow-x: hidden; padding-left: .7em; }
+.tabsContainer .activeTab { background-color: var(--mainBG); }
+
+.tabsContent { display: flex; overflow-y: auto; overflow-x: hidden; padding-left: .7em; padding-right: .7em; padding-bottom: .7em; }
.skillStoreSkillInfoContainer { margin-top: .4em; display: flex; width: 100%; flex-flow: row; }
.skillStoreSkillLeft { flex: 1 }
.skillStoreSkillRight { display: flex; margin-right: 5px; align-items: center; justify-content: flex-end; font-size: 3.5em; }
-.skillStoreSkillAuthor,.skillStoreSkillVersion,.skillStoreSkillCategory,.skillVersion,
-.spaced{ margin: .2em; }
+.skillStoreSkillAuthor, .skillStoreSkillVersion, .skillStoreSkillCategory, .skillVersion,
+.spaced { margin: .2em; font-size: 0.8em; }
/* bigger icon for FA */
.spaced i { width: 1.2em; }
/* Tile structure */
.tileContainer { margin: .2em; overflow-y: auto; overflow-x: hidden; display: flex; flex-wrap: wrap; align-content: flex-start; flex-direction: row; }
+
.tile { background-color: var(--windowBG); margin: .6em; display: flex; flex-wrap: wrap; align-content: flex-start; position: relative; box-shadow: 0 0 20px 5px #141414; }
-.skillStoreSkillTile { width: 18.75em; height: 18.75em; }
-.skillTile { width: 18.75em; height: 13em; }
-.skillStoreSkillDescription { background-color: var(--secondary); font-family: var(--long); font-size: 0.75em; width: 100%; height: 10em; margin: .5em; padding: .5em; box-sizing: border-box; overflow-x: hidden; overflow-y: auto; }
+.skillStoreSkillTile { width: 18.75em; height: 18.75em; font-family: var(--readable); }
+
+.skillTile { width: 18.75em; height: 13em; font-family: var(--readable); font-size: 1em; }
+
+.skillStoreSkillDescription { background-color: var(--secondary); font-family: var(--long); font-size: 0.75em; width: 100%; height: 10em; margin: .5em; padding: .5em; box-sizing: border-box; overflow-x: hidden; overflow-y: auto; }
.skillContainer { position: relative; flex-wrap: wrap; align-content: flex-start; height: 11.3em; margin: .2em; }
+
.skillDefaultView { display: flex; }
+
.skillIntentsView { width: 100%; overflow-x: hidden; overflow-y: auto; }
-.skillTitle { background-color: var(--secondary); height: 1.5em; width: 100%; padding: 0 .4em; display: flex; align-items: center; justify-content: space-between; overflow: hidden; }
+.skillTitle { background-color: var(--secondary); height: 1.5em; width: 100%; padding: 0 .4em; display: flex; align-items: center; justify-content: space-between; overflow: hidden; font-family: var(--short)}
+
.skillStatus { width: 100%; margin-top: 10px; margin-left: 4px; }
+
.skillName { overflow: hidden; font-size: 1.25em; }
.skillViewIntents,
@@ -174,9 +193,11 @@ button.button{ font-size: 1.6em; height: 2em; }
.skillInstructions { width: 100%; margin: .2em; text-align: right; }
/* Configuration Screens */
-.configBox { width: 100%; display: table; font-family: var(--long); background-color: var(--secondary); border-spacing: 0 10px; }
+.configBox { width: 100%; font-family: var(--readable); background-color: var(--secondary); border-spacing: 0 10px; }
+
.configCategory { width: 100%; display: table; margin-bottom: 30px; font-family: var(--long); background-color: var(--secondary); border-spacing: 0 10px; }
-.configLine { width: 100%; display: table-row; }
+
+.configLine { width: 100%; display: table-row; font-family: var(--readable); }
.configLine input, select, textarea { width: 500px; }
diff --git a/core/interface/static/js/admin.js b/core/interface/static/js/admin.js
index 21019bc88..be43d7810 100644
--- a/core/interface/static/js/admin.js
+++ b/core/interface/static/js/admin.js
@@ -37,7 +37,7 @@ $(function () {
let $icon = $div.children('.utilityIcon').children('i');
$icon.addClass('fa-spin red');
$.ajax({
- url: '/admin/' + endpoint + '/',
+ url : '/admin/' + endpoint + '/',
type: 'POST'
});
setTimeout(function () {
@@ -45,42 +45,42 @@ $(function () {
}, timeout);
}
- $('#restart').on('click touchstart', function () {
+ $('#restart').on('click touch', function () {
handleUtilityClick($(this), 'restart', 5000);
return false;
});
- $('#reboot').on('click touchstart', function () {
+ $('#reboot').on('click touch', function () {
handleUtilityClick($(this), 'reboot', 10000);
return false;
});
- $('#trainAssistant').on('click touchstart', function () {
+ $('#trainAssistant').on('click touch', function () {
handleUtilityClick($(this), 'trainAssistant', 5000);
return false;
});
- $('#wipeAll').on('click touchstart', function () {
+ $('#wipeAll').on('click touch', function () {
handleUtilityClick($(this), 'wipeAll', 5000);
return false;
});
- $('#update').on('click touchstart', function () {
+ $('#update').on('click touch', function () {
handleUtilityClick($(this), 'updatee', 5000);
return false;
});
- $('#addUser').on('click touchstart', function () {
+ $('#addUser').on('click touch', function () {
handleUtilityClick($(this), 'addUser', 1000);
return false;
});
- $('#addWakeword').on('click touchstart', function () {
+ $('#addWakeword').on('click touch', function () {
handleUtilityClick($(this), 'addWakeword', 1000);
return false;
});
- $('#tuneWakeword').on('click touchstart', function () {
+ $('#tuneWakeword').on('click touch', function () {
handleUtilityClick($(this), 'tuneWakeword', 1000);
return false;
});
diff --git a/core/interface/static/js/adminAuth.js b/core/interface/static/js/adminAuth.js
index 9429c19bb..7d6951ad2 100644
--- a/core/interface/static/js/adminAuth.js
+++ b/core/interface/static/js/adminAuth.js
@@ -23,7 +23,7 @@ $(function () {
$('#username').on('keyup', function () {
$.ajax({
- url: '/adminAuth/login/',
+ url : '/adminAuth/login/',
data: {
'username': $(this).val()
},
@@ -31,7 +31,7 @@ $(function () {
});
});
- $('.adminAuthKeyboardKey').not('.erase').not('.backspace').on('click touchstart', function () {
+ $('.adminAuthKeyboardKey').not('.erase').not('.backspace').on('click touch', function () {
if (!keyboardAuthNotified) {
$.post('/adminAuth/keyboardAuth/');
keyboardAuthNotified = true;
@@ -64,16 +64,16 @@ $(function () {
return true;
});
- $('.erase').on('click touchstart', function () {
+ $('.erase').on('click touch', function () {
code = '';
$('#codeContainer').children('.adminAuthDisplayDigit').each(function () {
$(this).removeClass('adminAuthDigitFilled');
});
});
- $('.backspace').on('click touchstart', function () {
+ $('.backspace').on('click touch', function () {
code = code.slice(0, -1);
- $('#codeContainer').children('.adminAuthDigitFilled').last().removeClass('adminAuthDigitFilled')
+ $('#codeContainer').children('.adminAuthDigitFilled').last().removeClass('adminAuthDigitFilled');
});
$('#adminAuthKeyboardContainer').hide();
diff --git a/core/interface/static/js/alicewatch.js b/core/interface/static/js/alicewatch.js
index dd505c388..78c4b33c3 100644
--- a/core/interface/static/js/alicewatch.js
+++ b/core/interface/static/js/alicewatch.js
@@ -55,23 +55,23 @@ $(function () {
'[' + time + '] [AliceWatch]Watching on ' + MQTT_HOST + ':' + MQTT_PORT + ' (MQTT)'
);
- MQTT.subscribe('projectalice/logging/alicewatch')
+ MQTT.subscribe('projectalice/logging/alicewatch');
}
- $stopScroll.on('click touchstart', function () {
+ $stopScroll.on('click touch', function () {
$(this).hide();
$startScroll.show();
return false;
});
- $startScroll.on('click touchstart', function () {
+ $startScroll.on('click touch', function () {
$(this).hide();
$stopScroll.show();
return false;
});
let $thermometers = $('[class^="fas fa-thermometer"]');
- $thermometers.on('click touchstart', function () {
+ $thermometers.on('click touch', function () {
$('[class^="fas fa-thermometer"]').removeClass('active');
$(this).addClass('active');
let level = $(this).data('verbosity');
diff --git a/core/interface/static/js/common.js b/core/interface/static/js/common.js
index 304f14dd4..545143219 100644
--- a/core/interface/static/js/common.js
+++ b/core/interface/static/js/common.js
@@ -3,13 +3,13 @@ let mqttSubscribers = {};
let MQTT_HOST;
let MQTT_PORT;
let LAST_CORE_HEARTBEAT = 0;
-let MAIN_GOING_DOWN = false
+let MAIN_GOING_DOWN = false;
function getCookie(cname) {
let name = cname + '=';
let decodedCookie = decodeURIComponent(document.cookie);
let ca = decodedCookie.split(';');
- for(let i = 0; i < ca.length; i++) {
+ for (let i = 0; i < ca.length; i++) {
let c = ca[i];
while (c.charAt(0) == ' ') {
c = c.substring(1);
@@ -33,57 +33,15 @@ function mqttRegisterSelf(target, method) {
mqttSubscribers[method].push(target);
}
-function initIndexers($element) {
- let indexer = $element.children('.zindexer');
-
- indexer.children('.zindexer-up').on('click touchscreen', function () {
- let $parent = $(this).parent().parent();
- let actualIndex = $element.css('z-index');
- if (actualIndex == null || actualIndex == 'auto') {
- actualIndex = 0;
- } else {
- actualIndex = parseInt(actualIndex);
- }
-
- let baseClass = $parent.attr('class').split(/\s+/)[0];
- $('.' + baseClass).each(function() {
- let thisIndex = $(this).css('z-index');
- if (thisIndex != null && thisIndex != 'auto' && parseInt(thisIndex) == actualIndex + 1) {
- $(this).css('z-index', actualIndex);
- $parent.css('z-index', actualIndex + 1);
- return false;
- }
- });
- });
-
- indexer.children('.zindexer-down').on('click touchscreen', function () {
- let $parent = $(this).parent().parent();
- let actualIndex = $element.css('z-index');
- if (actualIndex == null || actualIndex == 'auto' || parseInt(actualIndex) <= 0) {
- actualIndex = 0;
- } else {
- actualIndex = parseInt(actualIndex);
- }
-
- let baseClass = $parent.attr('class').split(/\s+/)[0];
- $('.' + baseClass).each(function() {
- let thisIndex = $(this).css('z-index');
- if (thisIndex != null && thisIndex != 'auto' && parseInt(thisIndex) == actualIndex -1) {
- $(this).css('z-index', actualIndex);
- $parent.css('z-index', actualIndex - 1);
- return false;
- }
- });
- });
-}
-
$(document).tooltip();
$(function () {
function onFailure(_msg) {
console.log('Mqtt connection failed, retry in 5 seconds');
- setTimeout(function() { connectMqtt(); }, 5000);
+ setTimeout(function () {
+ connectMqtt();
+ }, 5000);
}
function onConnect(msg) {
@@ -96,7 +54,7 @@ $(function () {
}
function onConnectionLost(resObj) {
- console.log('Mqtt disconnected, automatic reconnect is enabled Error code: ' + resObj.errorCode +' - '+ resObj.errorMessage );
+ console.log('Mqtt disconnected, automatic reconnect is enabled Error code: ' + resObj.errorCode + ' - ' + resObj.errorMessage);
connectMqtt();
}
@@ -123,7 +81,7 @@ $(function () {
MQTT_HOST = window.location.hostname;
}
let randomNum = Math.floor((Math.random() * 10000000) + 1);
- MQTT = new Paho.MQTT.Client(MQTT_HOST, MQTT_PORT, 'ProjectAliceInterface'+randomNum);
+ MQTT = new Paho.MQTT.Client(MQTT_HOST, MQTT_PORT, 'ProjectAliceInterface' + randomNum);
MQTT.onMessageArrived = onMessage;
MQTT.onConnectionLost = onConnectionLost;
MQTT.connect({
@@ -132,12 +90,16 @@ $(function () {
timeout : 5
});
} else {
- console.log('Failed fetching MQTT settings')
- setTimeout(function() { connectMqtt(); }, 5000);
+ console.log('Failed fetching MQTT settings');
+ setTimeout(function () {
+ connectMqtt();
+ }, 5000);
}
}).fail(function () {
- console.log("Coulnd't get MQTT information")
- setTimeout(function() { connectMqtt(); }, 5000);
+ console.log('Coulnd\'t get MQTT information');
+ setTimeout(function () {
+ connectMqtt();
+ }, 5000);
});
}
@@ -171,17 +133,14 @@ $(function () {
} else if (payload.status == 'done') {
$container.text('Nlu training done!');
}
- }
- else if (msg.topic == 'projectalice/skills/instructions') {
+ } else if (msg.topic == 'projectalice/skills/instructions') {
let payload = JSON.parse(msg.payloadString);
$('#skillInstructions').show();
let $content = $('#skillInstructionsContent');
$content.html($content.html() + payload['instructions']);
- }
- else if (msg.topic == 'projectalice/devices/coreHeartbeat') {
+ } else if (msg.topic == 'projectalice/devices/coreHeartbeat') {
LAST_CORE_HEARTBEAT = Date.now();
- }
- else if (msg.topic == 'projectalice/skills/coreConfigUpdateWarning') {
+ } else if (msg.topic == 'projectalice/skills/coreConfigUpdateWarning') {
$('#serverUnavailable').hide();
let $nodal = $('#coreConfigUpdateAlert');
$nodal.show();
@@ -190,12 +149,12 @@ $(function () {
let $container = $('#overlaySkillContent');
if ($container.children().length <= 0) {
- $container.append('