proper package
This commit is contained in:
parent
f404bbbc46
commit
446deaa962
12 changed files with 1074 additions and 733 deletions
203
LICENSE
Normal file
203
LICENSE
Normal file
|
@ -0,0 +1,203 @@
|
|||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright 2022 Casimiro Ferreira
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
|
10
README.md
10
README.md
|
@ -4,11 +4,19 @@ a simple UI for [OVOS-local-backend](https://github.com/OpenVoiceOS/OVOS-local-b
|
|||
|
||||

|
||||
|
||||
## Install
|
||||
|
||||
`pip install ovos-backend-manager`
|
||||
|
||||
or from source
|
||||
|
||||
`pip install git+https://github.com/OpenVoiceOS/ovos-personal-backend-ui`
|
||||
|
||||
## Usage
|
||||
|
||||
It needs to run on the same machine, it directly interacts with the backend database and configuration files
|
||||
|
||||
run app.py
|
||||
`ovos-backend-manager` will be available in the command line after installing
|
||||
|
||||
## Screenshots
|
||||
|
||||
|
|
732
app.py
732
app.py
|
@ -1,732 +0,0 @@
|
|||
import json
|
||||
import os
|
||||
import time
|
||||
from uuid import uuid4
|
||||
|
||||
from ovos_local_backend.configuration import CONFIGURATION
|
||||
from ovos_local_backend.database.metrics import JsonMetricDatabase
|
||||
from ovos_local_backend.database.settings import DeviceDatabase
|
||||
from ovos_local_backend.database.utterances import JsonUtteranceDatabase
|
||||
from ovos_local_backend.database.wakewords import JsonWakeWordDatabase
|
||||
from ovos_local_backend.utils import generate_code
|
||||
from ovos_local_backend.utils.geolocate import get_location_config
|
||||
from pywebio import start_server
|
||||
from pywebio.input import textarea, select, actions, checkbox, input_group, input, TEXT, NUMBER
|
||||
from pywebio.output import put_text, put_table, put_markdown, popup, put_code
|
||||
|
||||
STT_CONFIGS = {
|
||||
"OpenVoiceOS (google proxy)": {"module": "ovos-stt-plugin-server", "url": "https://stt.openvoiceos.com/stt"},
|
||||
"Selene": {"module": "ovos-stt-plugin-selene", "url": "https://api.mycroft.ai"},
|
||||
"Vosk (en-us) - small": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "http://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip"},
|
||||
"Vosk (en-us) - large": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-en-us-aspire-0.2.zip"},
|
||||
"Vosk (fr) - small": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-small-fr-pguyot-0.3.zip"},
|
||||
"Vosk (fr) - large": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://github.com/pguyot/zamia-speech/releases/download/20190930/kaldi-generic-fr-tdnn_f-r20191016.tar.xz"},
|
||||
"Vosk (de) - small": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-small-de-0.15.zip"},
|
||||
"Vosk (de) - large": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-de-0.6.zip"},
|
||||
"Vosk (es)": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-small-es-0.3.zip"},
|
||||
"Vosk (pt)": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-small-pt-0.3.zip"},
|
||||
"Vosk (it)": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-small-it-0.4.zip"},
|
||||
"Vosk (ca)": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-small-ca-0.4.zip"},
|
||||
"Vosk (nl) - small": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-nl-spraakherkenning-0.6-lgraph.zip"},
|
||||
"Vosk (nl) - large": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-nl-spraakherkenning-0.6.zip"}
|
||||
}
|
||||
|
||||
|
||||
def _device_uuid_menu(uuid, view=False, identity=None):
|
||||
device = DeviceDatabase().get_device(uuid)
|
||||
if device:
|
||||
if view:
|
||||
with popup(f'UUID: {device.uuid}'):
|
||||
|
||||
if identity:
|
||||
put_markdown(f'### identity2.json')
|
||||
put_code(json.dumps(identity, indent=4), "json")
|
||||
|
||||
put_markdown(f'### Device Data:')
|
||||
put_table([
|
||||
['Name', device.name],
|
||||
['Location', device.device_location],
|
||||
['Email', device.email],
|
||||
['Date Format', device.date_format],
|
||||
['Time Format', device.time_format],
|
||||
['System Unit', device.system_unit],
|
||||
['Opt In', device.opt_in],
|
||||
['Lang', device.lang],
|
||||
['Default Wake Word', device.default_ww],
|
||||
['Default Voice', device.default_tts]
|
||||
])
|
||||
put_markdown(f'### Geolocation:')
|
||||
put_table([
|
||||
['City', device.location["city"]["name"]],
|
||||
['State', device.location["city"]["state"]["name"]],
|
||||
['Country', device.location["city"]["state"]["country"]["name"]],
|
||||
['Country Code', device.location["city"]["state"]["country"]["code"]],
|
||||
['Latitude', device.location["coordinate"]["latitude"]],
|
||||
['Longitude', device.location["coordinate"]["longitude"]],
|
||||
['Timezone Code', device.location["timezone"]["code"]]
|
||||
])
|
||||
|
||||
opt = actions(label="What would you like to do?",
|
||||
buttons=[{'label': '<- Go Back', 'value': "main"},
|
||||
{'label': "View device configuration", 'value': "view"},
|
||||
{'label': "View device identity", 'value': "identity"},
|
||||
{'label': 'Change device name', 'value': "name"},
|
||||
{'label': 'Change placement', 'value': "location"},
|
||||
{'label': 'Change geographical location', 'value': "geo"},
|
||||
{'label': 'Change wake word', 'value': "ww"},
|
||||
{'label': 'Change voice', 'value': "tts"},
|
||||
{'label': 'Change email', 'value': "email"},
|
||||
{'label': 'Change opt-in', 'value': "opt-in"},
|
||||
{'label': 'Change date format', 'value': "date"},
|
||||
{'label': 'Change time format', 'value': "time"},
|
||||
{'label': 'Change system units', 'value': "unit"},
|
||||
{'label': 'Delete device', 'value': "delete"}])
|
||||
with DeviceDatabase() as db:
|
||||
if opt == "main":
|
||||
_device_menu()
|
||||
return
|
||||
if opt == "delete":
|
||||
with popup("Are you sure you want to delete the device?"):
|
||||
put_text("this can not be undone, proceed with caution!")
|
||||
opt = actions(label="Delete device?",
|
||||
buttons=[{'label': "yes", 'value': True},
|
||||
{'label': "no", 'value': False}])
|
||||
if opt:
|
||||
db.delete_device(uuid)
|
||||
db.store()
|
||||
_device_menu()
|
||||
return
|
||||
_device_uuid_menu(uuid)
|
||||
return
|
||||
if opt == "identity":
|
||||
identity = {"uuid": device.uuid,
|
||||
"expires_at": time.time() + 99999999999999,
|
||||
"accessToken": device.token,
|
||||
"refreshToken": device.token}
|
||||
_device_uuid_menu(uuid, view=True, identity=identity)
|
||||
return
|
||||
if opt == "opt-in":
|
||||
opt_in = checkbox("Opt-in to open dataset",
|
||||
[{'label': 'opt-in', 'value': True},
|
||||
{'label': 'selene_blacklist', 'value': False}])
|
||||
device.opt_in = opt_in[0]
|
||||
if opt_in[1]:
|
||||
CONFIGURATION["selene"]["opt_in_blacklist"].append(uuid)
|
||||
CONFIGURATION.store()
|
||||
if opt == "tts":
|
||||
tts = select("Choose a voice",
|
||||
list(CONFIGURATION["tts_configs"].keys()))
|
||||
device.default_tts = CONFIGURATION["tts_configs"][tts]["module"]
|
||||
device.default_tts_cfg = CONFIGURATION["tts_configs"][tts]
|
||||
if opt == "ww":
|
||||
ww = select("Choose a wake word",
|
||||
list(CONFIGURATION["ww_configs"].keys()))
|
||||
device.default_ww = ww
|
||||
device.default_ww_cfg = CONFIGURATION["ww_configs"][ww]
|
||||
if opt == "date":
|
||||
date = select("Change date format",
|
||||
['DMY', 'MDY'])
|
||||
device.date_format = date
|
||||
if opt == "time":
|
||||
tim = select("Change time format",
|
||||
['full', 'short'])
|
||||
device.time_format = tim
|
||||
if opt == "unit":
|
||||
unit = select("Change system units",
|
||||
['metric', 'imperial'])
|
||||
device.system_unit = unit
|
||||
if opt == "email":
|
||||
email = textarea("Enter your device email",
|
||||
placeholder="notify@me.com",
|
||||
required=True)
|
||||
device.email = email
|
||||
if opt == "name":
|
||||
name = textarea("Enter your device name",
|
||||
placeholder="OVOS Mark2",
|
||||
required=True)
|
||||
device.name = name
|
||||
if opt == "location":
|
||||
loc = textarea("Enter your device placement",
|
||||
placeholder="kitchen",
|
||||
required=True)
|
||||
device.device_location = loc
|
||||
if opt == "geo":
|
||||
loc = textarea("Enter an address",
|
||||
placeholder="Anywhere street Any city Nº234",
|
||||
required=True)
|
||||
data = get_location_config(loc)
|
||||
device.location = data
|
||||
|
||||
db.update_device(device)
|
||||
|
||||
popup("Device updated!")
|
||||
|
||||
_device_uuid_menu(uuid, view=opt == "view")
|
||||
else:
|
||||
popup(f"Device not found! Please verify uuid")
|
||||
_device_menu()
|
||||
|
||||
|
||||
def _selene_menu(view=False):
|
||||
if view:
|
||||
with popup("Selene Proxy Configuration"):
|
||||
put_table([
|
||||
['Enabled', CONFIGURATION["selene"]["enabled"]],
|
||||
['Host', CONFIGURATION["selene"]["url"]],
|
||||
['Version', CONFIGURATION["selene"]["version"]],
|
||||
['Identity', CONFIGURATION["selene"]["identity_file"]],
|
||||
['Proxy Pairing Enabled', CONFIGURATION["selene"]["proxy_pairing"]],
|
||||
['Proxy Weather', CONFIGURATION["selene"]["proxy_weather"]],
|
||||
['Proxy WolframAlpha', CONFIGURATION["selene"]["proxy_wolfram"]],
|
||||
['Proxy Geolocation', CONFIGURATION["selene"]["proxy_geolocation"]],
|
||||
['Proxy Email', CONFIGURATION["selene"]["proxy_email"]],
|
||||
['Download Location', CONFIGURATION["selene"]["download_location"]],
|
||||
['Download Preferences', CONFIGURATION["selene"]["download_prefs"]],
|
||||
['Download Skill Settings', CONFIGURATION["selene"]["download_settings"]],
|
||||
['Upload Skill Settings', CONFIGURATION["selene"]["upload_settings"]],
|
||||
['Force 2 way Skill Settings sync', CONFIGURATION["selene"]["force2way"]],
|
||||
['OpenDataset opt in', CONFIGURATION["selene"]["opt_in"]],
|
||||
['Upload Metrics', CONFIGURATION["selene"]["upload_metrics"]],
|
||||
['Upload Wake Words', CONFIGURATION["selene"]["upload_wakewords"]],
|
||||
['Upload Utterances', CONFIGURATION["selene"]["upload_utterances"]]
|
||||
])
|
||||
|
||||
if CONFIGURATION["selene"]["enabled"]:
|
||||
buttons = [{'label': '<- Go Back', 'value': "main"},
|
||||
{'label': "View configuration", 'value': "view"},
|
||||
{'label': "Disable Selene", 'value': "selene"}]
|
||||
|
||||
label = "Enable Proxy Pairing" if CONFIGURATION["selene"]["proxy_pairing"] else "Disable Proxy Pairing"
|
||||
buttons.insert(-2, {'label': label, 'value': "proxy"})
|
||||
label = "Enable Weather Proxy" if CONFIGURATION["selene"]["proxy_weather"] else "Disable Weather Proxy"
|
||||
buttons.insert(-2, {'label': label, 'value': "weather"})
|
||||
label = "Enable WolframAlpha Proxy" if CONFIGURATION["selene"][
|
||||
"proxy_wolfram"] else "Disable WolframAlpha Proxy"
|
||||
buttons.insert(-2, {'label': label, 'value': "wolfram"})
|
||||
label = "Enable Geolocation Proxy" if CONFIGURATION["selene"][
|
||||
"proxy_geolocation"] else "Disable Geolocation Proxy"
|
||||
buttons.insert(-2, {'label': label, 'value': "geolocation"})
|
||||
label = "Enable Email Proxy" if CONFIGURATION["selene"]["proxy_email"] else "Disable Email Proxy"
|
||||
buttons.insert(-2, {'label': label, 'value': "email"})
|
||||
label = "Enable Location Download" if CONFIGURATION["selene"][
|
||||
"download_location"] else "Disable Location Download"
|
||||
buttons.insert(-2, {'label': label, 'value': "location"})
|
||||
label = "Enable Preferences Download" if CONFIGURATION["selene"][
|
||||
"download_prefs"] else "Disable Preferences Download"
|
||||
buttons.insert(-2, {'label': label, 'value': "prefs"})
|
||||
label = "Enable SkillSettings Download" if CONFIGURATION["selene"][
|
||||
"download_settings"] else "Disable SkillSettings Download"
|
||||
buttons.insert(-2, {'label': label, 'value': "download_settings"})
|
||||
label = "Enable SkillSettings Upload" if CONFIGURATION["selene"][
|
||||
"upload_settings"] else "Disable SkillSettings Upload"
|
||||
buttons.insert(-2, {'label': label, 'value': "upload_settings"})
|
||||
label = "Enable forced 2way sync" if CONFIGURATION["selene"]["force2way"] else "Disable forced 2way sync"
|
||||
buttons.insert(-2, {'label': label, 'value': "2way"})
|
||||
label = "Enable Open Dataset Opt In" if CONFIGURATION["selene"]["opt_in"] else "Disable Open Dataset Opt In"
|
||||
buttons.insert(-2, {'label': label, 'value': "opt_in"})
|
||||
label = "Enable Metrics Upload" if CONFIGURATION["selene"]["upload_metrics"] else "Disable Metrics Upload"
|
||||
buttons.insert(-2, {'label': label, 'value': "metrics"})
|
||||
label = "Enable Wake Words Upload" if CONFIGURATION["selene"][
|
||||
"upload_wakewords"] else "Disable Wake Words Upload"
|
||||
buttons.insert(-2, {'label': label, 'value': "ww"})
|
||||
label = "Enable Utterances Upload" if CONFIGURATION["selene"][
|
||||
"upload_utterances"] else "Disable Utterances Upload"
|
||||
buttons.insert(-2, {'label': label, 'value': "stt"})
|
||||
|
||||
else:
|
||||
buttons = [{'label': '<- Go Back', 'value': "main"},
|
||||
{'label': "View configuration", 'value': "view"},
|
||||
{'label': "Enable Selene", 'value': "selene"}
|
||||
]
|
||||
|
||||
opt = actions(label="What would you like to do?", buttons=buttons)
|
||||
if opt == "main":
|
||||
_main_menu()
|
||||
return
|
||||
elif opt == "geolocation":
|
||||
CONFIGURATION["selene"]["proxy_geolocation"] = not CONFIGURATION["selene"]["proxy_geolocation"]
|
||||
elif opt == "weather":
|
||||
CONFIGURATION["selene"]["proxy_weather"] = not CONFIGURATION["selene"]["proxy_weather"]
|
||||
elif opt == "wolfram":
|
||||
CONFIGURATION["selene"]["proxy_wolfram"] = not CONFIGURATION["selene"]["proxy_wolfram"]
|
||||
elif opt == "email":
|
||||
CONFIGURATION["selene"]["proxy_email"] = not CONFIGURATION["selene"]["proxy_email"]
|
||||
elif opt == "proxy":
|
||||
CONFIGURATION["selene"]["proxy_pairing"] = not CONFIGURATION["selene"]["proxy_pairing"]
|
||||
elif opt == "location":
|
||||
CONFIGURATION["selene"]["download_location"] = not CONFIGURATION["selene"]["download_location"]
|
||||
elif opt == "prefs":
|
||||
CONFIGURATION["selene"]["download_prefs"] = not CONFIGURATION["selene"]["download_prefs"]
|
||||
elif opt == "download_settings":
|
||||
CONFIGURATION["selene"]["download_settings"] = not CONFIGURATION["selene"]["download_settings"]
|
||||
elif opt == "upload_settings":
|
||||
CONFIGURATION["selene"]["upload_settings"] = not CONFIGURATION["selene"]["upload_settings"]
|
||||
elif opt == "2way":
|
||||
CONFIGURATION["selene"]["force2way"] = not CONFIGURATION["selene"]["force2way"]
|
||||
elif opt == "opt_in":
|
||||
CONFIGURATION["selene"]["opt_in"] = not CONFIGURATION["selene"]["opt_in"]
|
||||
elif opt == "selene":
|
||||
CONFIGURATION["selene"]["enabled"] = not CONFIGURATION["selene"]["enabled"]
|
||||
elif opt == "stt":
|
||||
CONFIGURATION["selene"]["upload_utterances"] = not CONFIGURATION["selene"]["upload_utterances"]
|
||||
elif opt == "ww":
|
||||
CONFIGURATION["selene"]["upload_wakewords"] = not CONFIGURATION["selene"]["upload_wakewords"]
|
||||
elif opt == "metrics":
|
||||
CONFIGURATION["selene"]["upload_metrics"] = not CONFIGURATION["selene"]["upload_metrics"]
|
||||
|
||||
_selene_menu(view=opt == "view")
|
||||
|
||||
|
||||
def _microservices_menu(view=False):
|
||||
selene = CONFIGURATION["selene"]["enabled"]
|
||||
if view:
|
||||
with popup("Microservices Configuration"):
|
||||
put_table([
|
||||
['STT module', CONFIGURATION["stt"]["module"]],
|
||||
['OVOS microservices fallback enabled', CONFIGURATION["microservices"]["ovos_fallback"]],
|
||||
|
||||
['WolframAlpha provider', CONFIGURATION["microservices"]["wolfram_provider"]],
|
||||
['Weather provider', CONFIGURATION["microservices"]["weather_provider"]],
|
||||
|
||||
['Selene WolframAlpha proxy enabled', selene and CONFIGURATION["selene"]["proxy_wolfram"]],
|
||||
['Selene OpenWeatherMap proxy enabled', selene and CONFIGURATION["selene"]["proxy_weather"]],
|
||||
['Selene Geolocation proxy enabled', selene and CONFIGURATION["selene"]["proxy_geolocation"]],
|
||||
['Selene Email proxy enabled', selene and CONFIGURATION["selene"]["proxy_email"]],
|
||||
|
||||
['WolframAlpha Key', CONFIGURATION["microservices"]["wolfram_key"]],
|
||||
['OpenWeatherMap Key', CONFIGURATION["microservices"]["owm_key"]]
|
||||
])
|
||||
|
||||
buttons = [{'label': '<- Go Back', 'value': "main"},
|
||||
{'label': "View configuration", 'value': "view"},
|
||||
{'label': 'Configure STT', 'value': "stt"},
|
||||
{'label': 'Configure Secrets', 'value': "secrets"},
|
||||
{'label': 'Configure SMTP', 'value': "smtp"},
|
||||
{'label': 'Configure Wolfram Alpha', 'value': "wolfram"},
|
||||
{'label': 'Configure Weather', 'value': "weather"}
|
||||
]
|
||||
|
||||
if CONFIGURATION["microservices"]["ovos_fallback"]:
|
||||
buttons.insert(-2, {'label': 'Disable OVOS microservices fallback', 'value': "ovos"})
|
||||
else:
|
||||
buttons.insert(-2, {'label': 'Enable OVOS microservices fallback', 'value': "ovos"})
|
||||
|
||||
opt = actions(label="What would you like to do?", buttons=buttons)
|
||||
if opt == "main":
|
||||
_main_menu()
|
||||
return
|
||||
elif opt == "weather":
|
||||
opts = ["ovos", "selene"] if selene else ["ovos"]
|
||||
if CONFIGURATION["microservices"]["owm_key"]:
|
||||
opts.append("local")
|
||||
provider = select("Choose a weather provider", opts)
|
||||
CONFIGURATION["microservices"]["owm_provider"] = provider
|
||||
if provider == "selene":
|
||||
CONFIGURATION["selene"]["proxy_weather"] = True
|
||||
elif opt == "wolfram":
|
||||
opts = ["ovos", "selene"] if selene else ["ovos"]
|
||||
if CONFIGURATION["microservices"]["wolfram_key"]:
|
||||
opts.append("local")
|
||||
provider = select("Choose a WolframAlpha provider", opts)
|
||||
CONFIGURATION["microservices"]["wolfram_provider"] = provider
|
||||
if provider == "selene":
|
||||
CONFIGURATION["selene"]["proxy_wolfram"] = True
|
||||
elif opt == "ovos":
|
||||
CONFIGURATION["microservices"]["ovos_fallback"] = not CONFIGURATION["microservices"]["ovos_fallback"]
|
||||
if CONFIGURATION["microservices"]["ovos_fallback"]:
|
||||
with popup("OVOS microservices fallback enabled"):
|
||||
put_text(
|
||||
"wolfram alpha and weather requests will be proxied trough OVOS services if local keys get rate limited/become invalid/are not set")
|
||||
else:
|
||||
with popup("OVOS microservices fallback disabled"):
|
||||
put_text("please set your own wolfram alpha and weather keys")
|
||||
elif opt == "stt":
|
||||
opts = list(STT_CONFIGS.keys())
|
||||
if "Selene" in opts and not selene:
|
||||
opts.remove("Selene")
|
||||
stt = select("Choose a speech to text engine", opts)
|
||||
cfg = dict(STT_CONFIGS[stt])
|
||||
m = cfg.pop("module")
|
||||
CONFIGURATION["stt"]["module"] = m
|
||||
CONFIGURATION["stt"][m] = cfg
|
||||
with popup(f"STT set to: {stt}"):
|
||||
put_code(json.dumps(cfg, ensure_ascii=True, indent=2), "json")
|
||||
elif opt == "secrets":
|
||||
data = input_group('Secret Keys', [
|
||||
input("WolframAlpha key", value=CONFIGURATION["microservices"]["wolfram_key"],
|
||||
type=TEXT, name='wolfram'),
|
||||
input("OpenWeatherMap key", value=CONFIGURATION["microservices"]["owm_key"],
|
||||
type=TEXT, name='owm')
|
||||
])
|
||||
CONFIGURATION["microservices"]["wolfram_key"] = data["wolfram"]
|
||||
CONFIGURATION["microservices"]["owm_key"] = data["owm"]
|
||||
popup("Secrets updated!")
|
||||
elif opt == "smtp":
|
||||
if "smtp" not in CONFIGURATION["email"]:
|
||||
CONFIGURATION["email"]["smtp"] = {}
|
||||
|
||||
data = input_group('SMTP Configuration', [
|
||||
input("Username", value=CONFIGURATION["email"]["smtp"].get("username", 'user'),
|
||||
type=TEXT, name='username'),
|
||||
input("Password", value=CONFIGURATION["email"]["smtp"].get("password", '***********'),
|
||||
type=TEXT, name='password'),
|
||||
input("Host", value=CONFIGURATION["email"]["smtp"].get("host", 'smtp.mailprovider.com'),
|
||||
type=TEXT, name='host'),
|
||||
input("Port", value=CONFIGURATION["email"]["smtp"].get("port", '465'),
|
||||
type=NUMBER, name='port')
|
||||
])
|
||||
|
||||
CONFIGURATION["email"]["smtp"]["username"] = data["username"]
|
||||
CONFIGURATION["email"]["smtp"]["password"] = data["password"]
|
||||
CONFIGURATION["email"]["smtp"]["host"] = data["host"]
|
||||
CONFIGURATION["email"]["smtp"]["port"] = data["port"]
|
||||
with popup(f"SMTP configuration for: {data['host']}"):
|
||||
put_code(json.dumps(data, ensure_ascii=True, indent=2), "json")
|
||||
|
||||
CONFIGURATION.store()
|
||||
_microservices_menu(view=opt == "view")
|
||||
|
||||
|
||||
def _backend_menu(view=False):
|
||||
if view:
|
||||
with popup("Backend Configuration"):
|
||||
put_table([
|
||||
['Backend Port', CONFIGURATION["backend_port"]],
|
||||
['Device Authentication enabled', not CONFIGURATION["skip_auth"]],
|
||||
['Location override enabled', CONFIGURATION["override_location"]],
|
||||
['IP Geolocation enabled', CONFIGURATION["geolocate"]],
|
||||
['Selene Proxy enabled', CONFIGURATION["selene"]["enabled"]],
|
||||
['Default TTS', CONFIGURATION["default_tts"]],
|
||||
['Default Wake Word', CONFIGURATION["default_ww"]],
|
||||
['Default date format', CONFIGURATION["date_format"]],
|
||||
['Default time format', CONFIGURATION["time_format"]],
|
||||
['Default system units', CONFIGURATION["system_unit"]]
|
||||
])
|
||||
put_markdown(f'### Default location:')
|
||||
put_table([
|
||||
['City', CONFIGURATION["default_location"]["city"]["name"]],
|
||||
['State', CONFIGURATION["default_location"]["city"]["state"]["name"]],
|
||||
['Country', CONFIGURATION["default_location"]["city"]["state"]["country"]["name"]],
|
||||
['Country Code', CONFIGURATION["default_location"]["city"]["state"]["country"]["code"]],
|
||||
['Latitude', CONFIGURATION["default_location"]["coordinate"]["latitude"]],
|
||||
['Longitude', CONFIGURATION["default_location"]["coordinate"]["longitude"]],
|
||||
['Timezone Code', CONFIGURATION["default_location"]["timezone"]["code"]]
|
||||
])
|
||||
|
||||
auth = 'Enable device auth' if not CONFIGURATION["skip_auth"] else 'Disable device auth'
|
||||
|
||||
buttons = [{'label': '<- Go Back', 'value': "main"},
|
||||
{'label': "View configuration", 'value': "view"},
|
||||
{'label': auth, 'value': "auth"},
|
||||
{'label': 'Set default location', 'value': "geo"},
|
||||
{'label': 'Set default voice', 'value': "tts"},
|
||||
{'label': 'Set default wake word', 'value': "ww"},
|
||||
{'label': 'Set default email', 'value': "email"},
|
||||
{'label': 'Set default date format', 'value': "date"},
|
||||
{'label': 'Set default time format', 'value': "time"},
|
||||
{'label': 'Set default system units', 'value': "unit"}
|
||||
]
|
||||
if CONFIGURATION["override_location"]:
|
||||
buttons.insert(-2, {'label': 'Enable IP geolocation', 'value': "ip_geo"})
|
||||
else:
|
||||
buttons.insert(-2, {'label': 'Enable location override', 'value': "loc_override"})
|
||||
|
||||
opt = actions(label="What would you like to do?", buttons=buttons)
|
||||
if opt == "main":
|
||||
_main_menu()
|
||||
return
|
||||
elif opt == "tts":
|
||||
tts = select("Choose a voice", list(CONFIGURATION["tts_configs"].keys()))
|
||||
CONFIGURATION["default_tts"] = tts
|
||||
with popup(f"Default TTS set to: {tts}"):
|
||||
put_code(json.dumps(CONFIGURATION["tts_configs"][tts], ensure_ascii=True, indent=2), "json")
|
||||
elif opt == "ww":
|
||||
ww = select("Choose a wake word",
|
||||
list(CONFIGURATION["ww_configs"].keys()))
|
||||
CONFIGURATION["default_ww"] = ww
|
||||
with popup(f"Default wake word set to: {ww}"):
|
||||
put_code(json.dumps(CONFIGURATION["ww_configs"][ww], ensure_ascii=True, indent=2), "json")
|
||||
elif opt == "geo":
|
||||
loc = textarea("Enter an address",
|
||||
placeholder="Anywhere street Any city Nº234",
|
||||
required=True)
|
||||
data = get_location_config(loc)
|
||||
CONFIGURATION["default_location"] = data
|
||||
with popup(f"Default location set to: {loc}"):
|
||||
put_code(json.dumps(data, ensure_ascii=True, indent=2), "json")
|
||||
elif opt == "loc_override":
|
||||
CONFIGURATION["override_location"] = True
|
||||
CONFIGURATION["geolocate"] = False
|
||||
popup("Location override enabled!")
|
||||
elif opt == "ip_geo":
|
||||
CONFIGURATION["geolocate"] = True
|
||||
CONFIGURATION["override_location"] = False
|
||||
popup("IP Geolocation enabled!")
|
||||
elif opt == "auth":
|
||||
CONFIGURATION["skip_auth"] = not CONFIGURATION["skip_auth"]
|
||||
if CONFIGURATION["skip_auth"]:
|
||||
popup("Device authentication enabled!")
|
||||
else:
|
||||
popup("Device authentication disabled! Pairing will not be needed")
|
||||
elif opt == "date":
|
||||
CONFIGURATION["date_format"] = select("Change date format",
|
||||
['DMY', 'MDY'])
|
||||
popup(f"Default date format set to: {CONFIGURATION['date_format']}")
|
||||
elif opt == "time":
|
||||
CONFIGURATION["time_format"] = select("Change time format",
|
||||
['full', 'short'])
|
||||
popup(f"Default time format set to: {CONFIGURATION['time_format']}")
|
||||
elif opt == "unit":
|
||||
CONFIGURATION["system_unit"] = select("Change system units",
|
||||
['metric', 'imperial'])
|
||||
popup(f"Default system units set to: {CONFIGURATION['system_unit']}")
|
||||
elif opt == "email":
|
||||
email = textarea("Enter default notifications email",
|
||||
placeholder="notify@me.com",
|
||||
required=True)
|
||||
CONFIGURATION["email"]["recipient"] = email
|
||||
if opt == "view":
|
||||
_backend_menu(view=True)
|
||||
else:
|
||||
CONFIGURATION.store()
|
||||
_backend_menu(view=False)
|
||||
|
||||
|
||||
def _metrics_menu():
|
||||
buttons = []
|
||||
db = JsonMetricDatabase()
|
||||
if not len(db):
|
||||
popup("No metrics uploaded yet!")
|
||||
_database_menu()
|
||||
return
|
||||
|
||||
for m in db:
|
||||
name = f"{m['metric_id']}-{m['metric_type']}"
|
||||
buttons.append({'label': name, 'value': m['metric_id']})
|
||||
buttons.insert(0, {'label': '<- Go Back', 'value': "main"})
|
||||
opt = actions(label="Select a metric to inspect",
|
||||
buttons=buttons)
|
||||
if opt == "main":
|
||||
_database_menu()
|
||||
return
|
||||
# id == db_position + 1
|
||||
name = f"{opt}-{db[opt - 1]['metric_type']}"
|
||||
with popup(name):
|
||||
put_code(json.dumps(db[opt - 1], indent=4), "json")
|
||||
_metrics_menu()
|
||||
|
||||
|
||||
def _ww_menu():
|
||||
buttons = []
|
||||
db = JsonWakeWordDatabase()
|
||||
if not len(db):
|
||||
popup("No wake words uploaded yet!")
|
||||
_database_menu()
|
||||
return
|
||||
|
||||
for m in db:
|
||||
name = f"{m['wakeword_id']}-{m['transcription']}"
|
||||
buttons.append({'label': name, 'value': m['wakeword_id']})
|
||||
buttons.insert(0, {'label': '<- Go Back', 'value': "main"})
|
||||
opt = actions(label="Select a WakeWord recording",
|
||||
buttons=buttons)
|
||||
if opt == "main":
|
||||
_database_menu()
|
||||
return
|
||||
# id == db_position + 1
|
||||
name = f"{opt}-{db[opt - 1]['transcription']}"
|
||||
with popup(name):
|
||||
put_code(json.dumps(db[opt - 1], indent=4), "json")
|
||||
_ww_menu()
|
||||
|
||||
|
||||
def _utt_menu():
|
||||
buttons = []
|
||||
db = JsonUtteranceDatabase()
|
||||
if not len(db):
|
||||
popup("No utterances uploaded yet!")
|
||||
_database_menu()
|
||||
return
|
||||
|
||||
for m in db:
|
||||
name = f"{m['utterance_id']}-{m['transcription']}"
|
||||
buttons.append({'label': name, 'value': m['utterance_id']})
|
||||
buttons.insert(0, {'label': '<- Go Back', 'value': "main"})
|
||||
opt = actions(label="Select a Utterance recording",
|
||||
buttons=buttons)
|
||||
if opt == "main":
|
||||
_database_menu()
|
||||
return
|
||||
|
||||
# id == db_position + 1
|
||||
name = f"{opt}-{db[opt - 1]['transcription']}"
|
||||
with popup(name):
|
||||
put_code(json.dumps(db[opt - 1], indent=4), "json")
|
||||
_utt_menu()
|
||||
|
||||
|
||||
def _database_menu():
|
||||
opt = actions(label="What would you like to do?",
|
||||
buttons=[{'label': '<- Go Back', 'value': "main"},
|
||||
{'label': 'View Metrics', 'value': "metrics"},
|
||||
{'label': 'View Wake Words', 'value': "ww"},
|
||||
{'label': 'View Utterances', 'value': "utt"},
|
||||
{'label': 'Delete metrics database', 'value': "delete_metrics"},
|
||||
{'label': 'Delete wake words database', 'value': "delete_ww"},
|
||||
{'label': 'Delete utterances database', 'value': "delete_utts"}
|
||||
])
|
||||
|
||||
if opt == "metrics":
|
||||
_metrics_menu()
|
||||
if opt == "ww":
|
||||
_ww_menu()
|
||||
if opt == "utt":
|
||||
_utt_menu()
|
||||
if opt == "delete_metrics":
|
||||
with popup("Are you sure you want to delete the metrics database?"):
|
||||
put_text("this can not be undone, proceed with caution!")
|
||||
put_text("ALL metrics will be lost")
|
||||
opt = actions(label="Delete metrics database?",
|
||||
buttons=[{'label': "yes", 'value': True},
|
||||
{'label': "no", 'value': False}])
|
||||
if opt:
|
||||
os.remove(JsonMetricDatabase().db.path)
|
||||
_main_menu()
|
||||
else:
|
||||
_database_menu()
|
||||
return
|
||||
if opt == "delete_ww":
|
||||
with popup("Are you sure you want to delete the wake word database?"):
|
||||
put_text("this can not be undone, proceed with caution!")
|
||||
put_text("ALL wake word recordings will be lost")
|
||||
opt = actions(label="Delete wake words database?",
|
||||
buttons=[{'label': "yes", 'value': True},
|
||||
{'label': "no", 'value': False}])
|
||||
if opt:
|
||||
# TODO - also remove files from path
|
||||
os.remove(JsonWakeWordDatabase().db.path)
|
||||
_main_menu()
|
||||
else:
|
||||
_database_menu()
|
||||
return
|
||||
if opt == "delete_utts":
|
||||
with popup("Are you sure you want to delete the utterance database?"):
|
||||
put_text("this can not be undone, proceed with caution!")
|
||||
put_text("ALL utterance recordings will be lost")
|
||||
opt = actions(label="Delete utterance recordings database?",
|
||||
buttons=[{'label': "yes", 'value': True},
|
||||
{'label': "no", 'value': False}])
|
||||
if opt:
|
||||
# TODO - also remove files from path
|
||||
os.remove(JsonUtteranceDatabase().db.path)
|
||||
_main_menu()
|
||||
else:
|
||||
_database_menu()
|
||||
return
|
||||
if opt == "main":
|
||||
_main_menu()
|
||||
return
|
||||
_database_menu()
|
||||
|
||||
|
||||
def _device_menu():
|
||||
devices = {uuid: f"{device['name']}@{device['device_location']}"
|
||||
for uuid, device in DeviceDatabase().items()}
|
||||
buttons = [{'label': '<- Go Back', 'value': "main"}] + \
|
||||
[{'label': d, 'value': uuid} for uuid, d in devices.items()] + \
|
||||
[{'label': 'Delete device database', 'value': "delete_devices"}]
|
||||
if devices:
|
||||
uuid = actions(label="What device would you like to manage?",
|
||||
buttons=buttons)
|
||||
if uuid == "main":
|
||||
_main_menu()
|
||||
return
|
||||
elif uuid == "delete_devices":
|
||||
with popup("Are you sure you want to delete the device database?"):
|
||||
put_text("this can not be undone, proceed with caution!")
|
||||
put_text("ALL devices will be unpaired")
|
||||
opt = actions(label="Delete devices database?",
|
||||
buttons=[{'label': "yes", 'value': True},
|
||||
{'label': "no", 'value': False}])
|
||||
if opt:
|
||||
os.remove(DeviceDatabase().path)
|
||||
_main_menu()
|
||||
else:
|
||||
_device_menu()
|
||||
return
|
||||
else:
|
||||
_device_uuid_menu(uuid, view=True)
|
||||
else:
|
||||
popup("No devices paired yet!")
|
||||
_main_menu()
|
||||
|
||||
|
||||
def _pair_device():
|
||||
uuid = str(uuid4())
|
||||
code = generate_code()
|
||||
token = f"{code}:{uuid}"
|
||||
# add device to db
|
||||
with DeviceDatabase() as db:
|
||||
db.add_device(uuid, token)
|
||||
|
||||
identity = {"uuid": uuid,
|
||||
"expires_at": time.time() + 99999999999999,
|
||||
"accessToken": token,
|
||||
"refreshToken": token}
|
||||
|
||||
_device_uuid_menu(uuid, view=True, identity=identity)
|
||||
|
||||
|
||||
def _main_menu():
|
||||
opt = actions(label="What would you like to do?",
|
||||
buttons=[{'label': 'Pair a device', 'value': "pair"},
|
||||
{'label': 'Manage Devices', 'value': "device"},
|
||||
{'label': 'Manage Datasets', 'value': "db"},
|
||||
{'label': 'Configure Backend', 'value': "backend"},
|
||||
{'label': 'Configure Microservices', 'value': "services"},
|
||||
{'label': 'Configure Selene Proxy', 'value': "selene"}])
|
||||
if opt == "pair":
|
||||
_pair_device()
|
||||
elif opt == "services":
|
||||
_microservices_menu()
|
||||
elif opt == "db":
|
||||
_database_menu()
|
||||
elif opt == "backend":
|
||||
_backend_menu(view=False)
|
||||
elif opt == "selene":
|
||||
_selene_menu()
|
||||
elif opt == "device":
|
||||
_device_menu()
|
||||
|
||||
|
||||
def _admin_auth():
|
||||
admin_key = textarea("insert your admin_key, this should have been set in your backend configuration file",
|
||||
placeholder="SuperSecretPassword1!",
|
||||
required=True)
|
||||
if CONFIGURATION["admin_key"] != admin_key:
|
||||
popup("INVALID ADMIN KEY!")
|
||||
_admin_auth()
|
||||
|
||||
|
||||
def app():
|
||||
if not CONFIGURATION["admin_key"]:
|
||||
put_text("This personal backend instance does not have the admin interface exposed")
|
||||
return
|
||||
_admin_auth()
|
||||
_main_menu()
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
start_server(app, port=36535, debug=True)
|
11
ovos_backend_manager/__main__.py
Normal file
11
ovos_backend_manager/__main__.py
Normal file
|
@ -0,0 +1,11 @@
|
|||
from pywebio import start_server
|
||||
|
||||
from ovos_backend_manager.app import app
|
||||
|
||||
|
||||
def main(port=36535):
|
||||
start_server(app, port=port, debug=False)
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
start_server(app, port=36535, debug=True)
|
48
ovos_backend_manager/app.py
Normal file
48
ovos_backend_manager/app.py
Normal file
|
@ -0,0 +1,48 @@
|
|||
from ovos_local_backend.configuration import CONFIGURATION
|
||||
from pywebio.input import textarea, actions
|
||||
from pywebio.output import put_text, popup
|
||||
|
||||
from ovos_backend_manager.backend import backend_menu
|
||||
from ovos_backend_manager.datasets import datasets_menu
|
||||
from ovos_backend_manager.devices import device_select, instant_pair
|
||||
from ovos_backend_manager.microservices import microservices_menu
|
||||
from ovos_backend_manager.selene import selene_menu
|
||||
|
||||
|
||||
def main_menu():
|
||||
opt = actions(label="What would you like to do?",
|
||||
buttons=[{'label': 'Pair a device', 'value': "pair"},
|
||||
{'label': 'Manage Devices', 'value': "device"},
|
||||
{'label': 'Manage Datasets', 'value': "db"},
|
||||
{'label': 'Configure Backend', 'value': "backend"},
|
||||
{'label': 'Configure Microservices', 'value': "services"},
|
||||
{'label': 'Configure Selene Proxy', 'value': "selene"}])
|
||||
if opt == "pair":
|
||||
instant_pair(back_handler=main_menu)
|
||||
elif opt == "services":
|
||||
microservices_menu(back_handler=main_menu)
|
||||
elif opt == "db":
|
||||
datasets_menu(back_handler=main_menu)
|
||||
elif opt == "backend":
|
||||
backend_menu(back_handler=main_menu)
|
||||
elif opt == "selene":
|
||||
selene_menu(back_handler=main_menu)
|
||||
elif opt == "device":
|
||||
device_select(back_handler=main_menu)
|
||||
|
||||
|
||||
def prompt_admin_key():
|
||||
admin_key = textarea("insert your admin_key, this should have been set in your backend configuration file",
|
||||
placeholder="SuperSecretPassword1!",
|
||||
required=True)
|
||||
if CONFIGURATION["admin_key"] != admin_key:
|
||||
popup("INVALID ADMIN KEY!")
|
||||
prompt_admin_key()
|
||||
|
||||
|
||||
def app():
|
||||
if not CONFIGURATION["admin_key"]:
|
||||
put_text("This personal backend instance does not have the admin interface exposed")
|
||||
exit(1)
|
||||
prompt_admin_key()
|
||||
main_menu()
|
111
ovos_backend_manager/backend.py
Normal file
111
ovos_backend_manager/backend.py
Normal file
|
@ -0,0 +1,111 @@
|
|||
import json
|
||||
|
||||
from ovos_local_backend.configuration import CONFIGURATION
|
||||
from ovos_local_backend.utils.geolocate import get_location_config
|
||||
from pywebio.input import textarea, select, actions
|
||||
from pywebio.output import put_table, put_markdown, popup, put_code
|
||||
|
||||
|
||||
def backend_menu(back_handler=None):
|
||||
auth = 'Enable device auth' if not CONFIGURATION["skip_auth"] else 'Disable device auth'
|
||||
|
||||
buttons = [{'label': "View configuration", 'value': "view"},
|
||||
{'label': auth, 'value': "auth"},
|
||||
{'label': 'Set default location', 'value': "geo"},
|
||||
{'label': 'Set default voice', 'value': "tts"},
|
||||
{'label': 'Set default wake word', 'value': "ww"},
|
||||
{'label': 'Set default email', 'value': "email"},
|
||||
{'label': 'Set default date format', 'value': "date"},
|
||||
{'label': 'Set default time format', 'value': "time"},
|
||||
{'label': 'Set default system units', 'value': "unit"}
|
||||
]
|
||||
if back_handler:
|
||||
buttons.insert(0, {'label': '<- Go Back', 'value': "main"})
|
||||
|
||||
if CONFIGURATION["override_location"]:
|
||||
buttons.insert(-2, {'label': 'Enable IP geolocation', 'value': "ip_geo"})
|
||||
else:
|
||||
buttons.insert(-2, {'label': 'Enable location override', 'value': "loc_override"})
|
||||
|
||||
opt = actions(label="What would you like to do?", buttons=buttons)
|
||||
if opt == "main":
|
||||
back_handler()
|
||||
return
|
||||
elif opt == "tts":
|
||||
tts = select("Choose a voice", list(CONFIGURATION["tts_configs"].keys()))
|
||||
CONFIGURATION["default_tts"] = tts
|
||||
with popup(f"Default TTS set to: {tts}"):
|
||||
put_code(json.dumps(CONFIGURATION["tts_configs"][tts], ensure_ascii=True, indent=2), "json")
|
||||
elif opt == "ww":
|
||||
ww = select("Choose a wake word",
|
||||
list(CONFIGURATION["ww_configs"].keys()))
|
||||
CONFIGURATION["default_ww"] = ww
|
||||
with popup(f"Default wake word set to: {ww}"):
|
||||
put_code(json.dumps(CONFIGURATION["ww_configs"][ww], ensure_ascii=True, indent=2), "json")
|
||||
elif opt == "geo":
|
||||
loc = textarea("Enter an address",
|
||||
placeholder="Anywhere street Any city Nº234",
|
||||
required=True)
|
||||
data = get_location_config(loc)
|
||||
CONFIGURATION["default_location"] = data
|
||||
with popup(f"Default location set to: {loc}"):
|
||||
put_code(json.dumps(data, ensure_ascii=True, indent=2), "json")
|
||||
elif opt == "loc_override":
|
||||
CONFIGURATION["override_location"] = True
|
||||
CONFIGURATION["geolocate"] = False
|
||||
popup("Location override enabled!")
|
||||
elif opt == "ip_geo":
|
||||
CONFIGURATION["geolocate"] = True
|
||||
CONFIGURATION["override_location"] = False
|
||||
popup("IP Geolocation enabled!")
|
||||
elif opt == "auth":
|
||||
CONFIGURATION["skip_auth"] = not CONFIGURATION["skip_auth"]
|
||||
if CONFIGURATION["skip_auth"]:
|
||||
popup("Device authentication enabled!")
|
||||
else:
|
||||
popup("Device authentication disabled! Pairing will not be needed")
|
||||
elif opt == "date":
|
||||
CONFIGURATION["date_format"] = select("Change date format",
|
||||
['DMY', 'MDY'])
|
||||
popup(f"Default date format set to: {CONFIGURATION['date_format']}")
|
||||
elif opt == "time":
|
||||
CONFIGURATION["time_format"] = select("Change time format",
|
||||
['full', 'short'])
|
||||
popup(f"Default time format set to: {CONFIGURATION['time_format']}")
|
||||
elif opt == "unit":
|
||||
CONFIGURATION["system_unit"] = select("Change system units",
|
||||
['metric', 'imperial'])
|
||||
popup(f"Default system units set to: {CONFIGURATION['system_unit']}")
|
||||
elif opt == "email":
|
||||
email = textarea("Enter default notifications email",
|
||||
placeholder="notify@me.com",
|
||||
required=True)
|
||||
CONFIGURATION["email"]["recipient"] = email
|
||||
|
||||
if opt == "view":
|
||||
with popup("Backend Configuration"):
|
||||
put_table([
|
||||
['Backend Port', CONFIGURATION["backend_port"]],
|
||||
['Device Authentication enabled', not CONFIGURATION["skip_auth"]],
|
||||
['Location override enabled', CONFIGURATION["override_location"]],
|
||||
['IP Geolocation enabled', CONFIGURATION["geolocate"]],
|
||||
['Selene Proxy enabled', CONFIGURATION["selene"]["enabled"]],
|
||||
['Default TTS', CONFIGURATION["default_tts"]],
|
||||
['Default Wake Word', CONFIGURATION["default_ww"]],
|
||||
['Default date format', CONFIGURATION["date_format"]],
|
||||
['Default time format', CONFIGURATION["time_format"]],
|
||||
['Default system units', CONFIGURATION["system_unit"]]
|
||||
])
|
||||
put_markdown(f'### Default location:')
|
||||
put_table([
|
||||
['City', CONFIGURATION["default_location"]["city"]["name"]],
|
||||
['State', CONFIGURATION["default_location"]["city"]["state"]["name"]],
|
||||
['Country', CONFIGURATION["default_location"]["city"]["state"]["country"]["name"]],
|
||||
['Country Code', CONFIGURATION["default_location"]["city"]["state"]["country"]["code"]],
|
||||
['Latitude', CONFIGURATION["default_location"]["coordinate"]["latitude"]],
|
||||
['Longitude', CONFIGURATION["default_location"]["coordinate"]["longitude"]],
|
||||
['Timezone Code', CONFIGURATION["default_location"]["timezone"]["code"]]
|
||||
])
|
||||
else:
|
||||
CONFIGURATION.store()
|
||||
backend_menu(back_handler=back_handler)
|
151
ovos_backend_manager/datasets.py
Normal file
151
ovos_backend_manager/datasets.py
Normal file
|
@ -0,0 +1,151 @@
|
|||
import json
|
||||
import os
|
||||
|
||||
from ovos_local_backend.database.metrics import JsonMetricDatabase
|
||||
from ovos_local_backend.database.utterances import JsonUtteranceDatabase
|
||||
from ovos_local_backend.database.wakewords import JsonWakeWordDatabase
|
||||
from pywebio.input import actions
|
||||
from pywebio.output import put_text, popup, put_code
|
||||
|
||||
|
||||
def metrics_select(back_handler=None):
|
||||
buttons = []
|
||||
db = JsonMetricDatabase()
|
||||
if not len(db):
|
||||
popup("No metrics uploaded yet!")
|
||||
datasets_menu(back_handler=back_handler)
|
||||
return
|
||||
|
||||
for m in db:
|
||||
name = f"{m['metric_id']}-{m['metric_type']}"
|
||||
buttons.append({'label': name, 'value': m['metric_id']})
|
||||
if back_handler:
|
||||
buttons.insert(0, {'label': '<- Go Back', 'value': "main"})
|
||||
opt = actions(label="Select a metric to inspect",
|
||||
buttons=buttons)
|
||||
if opt == "main":
|
||||
datasets_menu(back_handler=back_handler)
|
||||
return
|
||||
# id == db_position + 1
|
||||
name = f"{opt}-{db[opt - 1]['metric_type']}"
|
||||
with popup(name):
|
||||
put_code(json.dumps(db[opt - 1], indent=4), "json")
|
||||
metrics_select()
|
||||
|
||||
|
||||
def ww_select(back_handler=None):
|
||||
buttons = []
|
||||
db = JsonWakeWordDatabase()
|
||||
if not len(db):
|
||||
popup("No wake words uploaded yet!")
|
||||
datasets_menu(back_handler=back_handler)
|
||||
return
|
||||
|
||||
for m in db:
|
||||
name = f"{m['wakeword_id']}-{m['transcription']}"
|
||||
buttons.append({'label': name, 'value': m['wakeword_id']})
|
||||
if back_handler:
|
||||
buttons.insert(0, {'label': '<- Go Back', 'value': "main"})
|
||||
opt = actions(label="Select a WakeWord recording",
|
||||
buttons=buttons)
|
||||
if opt == "main":
|
||||
datasets_menu(back_handler=back_handler)
|
||||
return
|
||||
# id == db_position + 1
|
||||
name = f"{opt}-{db[opt - 1]['transcription']}"
|
||||
with popup(name):
|
||||
put_code(json.dumps(db[opt - 1], indent=4), "json")
|
||||
ww_select()
|
||||
|
||||
|
||||
def utt_select(back_handler=None):
|
||||
buttons = []
|
||||
db = JsonUtteranceDatabase()
|
||||
if not len(db):
|
||||
popup("No utterances uploaded yet!")
|
||||
datasets_menu(back_handler=back_handler)
|
||||
return
|
||||
|
||||
for m in db:
|
||||
name = f"{m['utterance_id']}-{m['transcription']}"
|
||||
buttons.append({'label': name, 'value': m['utterance_id']})
|
||||
if back_handler:
|
||||
buttons.insert(0, {'label': '<- Go Back', 'value': "main"})
|
||||
opt = actions(label="Select a Utterance recording",
|
||||
buttons=buttons)
|
||||
if opt == "main":
|
||||
datasets_menu(back_handler=back_handler)
|
||||
return
|
||||
|
||||
# id == db_position + 1
|
||||
name = f"{opt}-{db[opt - 1]['transcription']}"
|
||||
with popup(name):
|
||||
put_code(json.dumps(db[opt - 1], indent=4), "json")
|
||||
utt_select()
|
||||
|
||||
|
||||
def datasets_menu(back_handler=None):
|
||||
buttons = [{'label': 'View Metrics', 'value': "metrics"},
|
||||
{'label': 'View Wake Words', 'value': "ww"},
|
||||
{'label': 'View Utterances', 'value': "utt"},
|
||||
{'label': 'Delete metrics database', 'value': "delete_metrics"},
|
||||
{'label': 'Delete wake words database', 'value': "delete_ww"},
|
||||
{'label': 'Delete utterances database', 'value': "delete_utts"}
|
||||
]
|
||||
if back_handler:
|
||||
buttons.insert(0, {'label': '<- Go Back', 'value': "main"})
|
||||
|
||||
opt = actions(label="What would you like to do?",
|
||||
buttons=buttons)
|
||||
|
||||
if opt == "metrics":
|
||||
metrics_select(back_handler=back_handler)
|
||||
if opt == "ww":
|
||||
ww_select(back_handler=back_handler)
|
||||
if opt == "utt":
|
||||
utt_select(back_handler=back_handler)
|
||||
if opt == "delete_metrics":
|
||||
with popup("Are you sure you want to delete the metrics database?"):
|
||||
put_text("this can not be undone, proceed with caution!")
|
||||
put_text("ALL metrics will be lost")
|
||||
opt = actions(label="Delete metrics database?",
|
||||
buttons=[{'label': "yes", 'value': True},
|
||||
{'label': "no", 'value': False}])
|
||||
if opt:
|
||||
os.remove(JsonMetricDatabase().db.path)
|
||||
back_handler()
|
||||
else:
|
||||
datasets_menu(back_handler=back_handler)
|
||||
return
|
||||
if opt == "delete_ww":
|
||||
with popup("Are you sure you want to delete the wake word database?"):
|
||||
put_text("this can not be undone, proceed with caution!")
|
||||
put_text("ALL wake word recordings will be lost")
|
||||
opt = actions(label="Delete wake words database?",
|
||||
buttons=[{'label': "yes", 'value': True},
|
||||
{'label': "no", 'value': False}])
|
||||
if opt:
|
||||
# TODO - also remove files from path
|
||||
os.remove(JsonWakeWordDatabase().db.path)
|
||||
back_handler()
|
||||
else:
|
||||
datasets_menu(back_handler=back_handler)
|
||||
return
|
||||
if opt == "delete_utts":
|
||||
with popup("Are you sure you want to delete the utterance database?"):
|
||||
put_text("this can not be undone, proceed with caution!")
|
||||
put_text("ALL utterance recordings will be lost")
|
||||
opt = actions(label="Delete utterance recordings database?",
|
||||
buttons=[{'label': "yes", 'value': True},
|
||||
{'label': "no", 'value': False}])
|
||||
if opt:
|
||||
# TODO - also remove files from path
|
||||
os.remove(JsonUtteranceDatabase().db.path)
|
||||
back_handler()
|
||||
else:
|
||||
datasets_menu(back_handler=back_handler)
|
||||
return
|
||||
if opt == "main":
|
||||
back_handler()
|
||||
return
|
||||
datasets_menu(back_handler=back_handler)
|
196
ovos_backend_manager/devices.py
Normal file
196
ovos_backend_manager/devices.py
Normal file
|
@ -0,0 +1,196 @@
|
|||
import json
|
||||
import os
|
||||
import time
|
||||
from uuid import uuid4
|
||||
|
||||
from ovos_local_backend.configuration import CONFIGURATION
|
||||
from ovos_local_backend.database.settings import DeviceDatabase
|
||||
from ovos_local_backend.utils import generate_code
|
||||
from ovos_local_backend.utils.geolocate import get_location_config
|
||||
from pywebio.input import textarea, select, actions, checkbox
|
||||
from pywebio.output import put_text, put_table, put_markdown, popup, put_code
|
||||
|
||||
|
||||
def device_menu(uuid, back_handler=None):
|
||||
buttons = [{'label': "View device configuration", 'value': "view"},
|
||||
{'label': "View device identity", 'value': "identity"},
|
||||
{'label': 'Change device name', 'value': "name"},
|
||||
{'label': 'Change placement', 'value': "location"},
|
||||
{'label': 'Change geographical location', 'value': "geo"},
|
||||
{'label': 'Change wake word', 'value': "ww"},
|
||||
{'label': 'Change voice', 'value': "tts"},
|
||||
{'label': 'Change email', 'value': "email"},
|
||||
{'label': 'Change opt-in', 'value': "opt-in"},
|
||||
{'label': 'Change date format', 'value': "date"},
|
||||
{'label': 'Change time format', 'value': "time"},
|
||||
{'label': 'Change system units', 'value': "unit"},
|
||||
{'label': 'Delete device', 'value': "delete"}]
|
||||
if back_handler:
|
||||
buttons.insert(0, {'label': '<- Go Back', 'value': "main"})
|
||||
device = DeviceDatabase().get_device(uuid)
|
||||
if device:
|
||||
opt = actions(label="What would you like to do?",
|
||||
buttons=buttons)
|
||||
with DeviceDatabase() as db:
|
||||
if opt == "main":
|
||||
device_select(back_handler)
|
||||
return
|
||||
if opt == "delete":
|
||||
with popup("Are you sure you want to delete the device?"):
|
||||
put_text("this can not be undone, proceed with caution!")
|
||||
opt = actions(label="Delete device?",
|
||||
buttons=[{'label': "yes", 'value': True},
|
||||
{'label': "no", 'value': False}])
|
||||
if opt:
|
||||
db.delete_device(uuid)
|
||||
db.store()
|
||||
device_select(back_handler)
|
||||
return
|
||||
device_menu(uuid, back_handler=back_handler)
|
||||
return
|
||||
if opt == "opt-in":
|
||||
opt_in = checkbox("Opt-in to open dataset",
|
||||
[{'label': 'opt-in', 'value': True},
|
||||
{'label': 'selene_blacklist', 'value': False}])
|
||||
device.opt_in = opt_in[0]
|
||||
if opt_in[1]:
|
||||
CONFIGURATION["selene"]["opt_in_blacklist"].append(uuid)
|
||||
CONFIGURATION.store()
|
||||
if opt == "tts":
|
||||
tts = select("Choose a voice",
|
||||
list(CONFIGURATION["tts_configs"].keys()))
|
||||
device.default_tts = CONFIGURATION["tts_configs"][tts]["module"]
|
||||
device.default_tts_cfg = CONFIGURATION["tts_configs"][tts]
|
||||
if opt == "ww":
|
||||
ww = select("Choose a wake word",
|
||||
list(CONFIGURATION["ww_configs"].keys()))
|
||||
device.default_ww = ww
|
||||
device.default_ww_cfg = CONFIGURATION["ww_configs"][ww]
|
||||
if opt == "date":
|
||||
date = select("Change date format",
|
||||
['DMY', 'MDY'])
|
||||
device.date_format = date
|
||||
if opt == "time":
|
||||
tim = select("Change time format",
|
||||
['full', 'short'])
|
||||
device.time_format = tim
|
||||
if opt == "unit":
|
||||
unit = select("Change system units",
|
||||
['metric', 'imperial'])
|
||||
device.system_unit = unit
|
||||
if opt == "email":
|
||||
email = textarea("Enter your device email",
|
||||
placeholder="notify@me.com",
|
||||
required=True)
|
||||
device.email = email
|
||||
if opt == "name":
|
||||
name = textarea("Enter your device name",
|
||||
placeholder="OVOS Mark2",
|
||||
required=True)
|
||||
device.name = name
|
||||
if opt == "location":
|
||||
loc = textarea("Enter your device placement",
|
||||
placeholder="kitchen",
|
||||
required=True)
|
||||
device.device_location = loc
|
||||
if opt == "geo":
|
||||
loc = textarea("Enter an address",
|
||||
placeholder="Anywhere street Any city Nº234",
|
||||
required=True)
|
||||
data = get_location_config(loc)
|
||||
device.location = data
|
||||
|
||||
if opt == "identity":
|
||||
identity = {"uuid": device.uuid,
|
||||
"expires_at": time.time() + 99999999999999,
|
||||
"accessToken": device.token,
|
||||
"refreshToken": device.token}
|
||||
with popup(f'UUID: {device.uuid}'):
|
||||
put_markdown(f'### identity2.json')
|
||||
put_code(json.dumps(identity, indent=4), "json")
|
||||
elif opt == "view":
|
||||
with popup(f'UUID: {device.uuid}'):
|
||||
put_markdown(f'### Device Data:')
|
||||
put_table([
|
||||
['Name', device.name],
|
||||
['Location', device.device_location],
|
||||
['Email', device.email],
|
||||
['Date Format', device.date_format],
|
||||
['Time Format', device.time_format],
|
||||
['System Unit', device.system_unit],
|
||||
['Opt In', device.opt_in],
|
||||
['Lang', device.lang],
|
||||
['Default Wake Word', device.default_ww],
|
||||
['Default Voice', device.default_tts]
|
||||
])
|
||||
put_markdown(f'### Geolocation:')
|
||||
put_table([
|
||||
['City', device.location["city"]["name"]],
|
||||
['State', device.location["city"]["state"]["name"]],
|
||||
['Country', device.location["city"]["state"]["country"]["name"]],
|
||||
['Country Code', device.location["city"]["state"]["country"]["code"]],
|
||||
['Latitude', device.location["coordinate"]["latitude"]],
|
||||
['Longitude', device.location["coordinate"]["longitude"]],
|
||||
['Timezone Code', device.location["timezone"]["code"]]
|
||||
])
|
||||
else:
|
||||
db.update_device(device)
|
||||
popup("Device updated!")
|
||||
|
||||
device_menu(uuid, back_handler=back_handler)
|
||||
else:
|
||||
popup(f"Device not found! Please verify uuid")
|
||||
device_select(back_handler)
|
||||
|
||||
|
||||
def device_select(back_handler=None):
|
||||
devices = {uuid: f"{device['name']}@{device['device_location']}"
|
||||
for uuid, device in DeviceDatabase().items()}
|
||||
buttons = [{'label': d, 'value': uuid} for uuid, d in devices.items()] + \
|
||||
[{'label': 'Delete device database', 'value': "delete_devices"}]
|
||||
if back_handler:
|
||||
buttons.insert(0, {'label': '<- Go Back', 'value': "main"})
|
||||
|
||||
if devices:
|
||||
uuid = actions(label="What device would you like to manage?",
|
||||
buttons=buttons)
|
||||
if uuid == "main":
|
||||
back_handler()
|
||||
return
|
||||
elif uuid == "delete_devices":
|
||||
with popup("Are you sure you want to delete the device database?"):
|
||||
put_text("this can not be undone, proceed with caution!")
|
||||
put_text("ALL devices will be unpaired")
|
||||
opt = actions(label="Delete devices database?",
|
||||
buttons=[{'label': "yes", 'value': True},
|
||||
{'label': "no", 'value': False}])
|
||||
if opt:
|
||||
os.remove(DeviceDatabase().path)
|
||||
back_handler()
|
||||
else:
|
||||
device_select(back_handler)
|
||||
return
|
||||
else:
|
||||
device_menu(uuid, back_handler=back_handler)
|
||||
else:
|
||||
popup("No devices paired yet!")
|
||||
if back_handler:
|
||||
back_handler()
|
||||
|
||||
|
||||
def instant_pair(back_handler=None):
|
||||
uuid = str(uuid4())
|
||||
code = generate_code()
|
||||
token = f"{code}:{uuid}"
|
||||
# add device to db
|
||||
with DeviceDatabase() as db:
|
||||
db.add_device(uuid, token)
|
||||
|
||||
with popup("Device paired!"):
|
||||
put_table([
|
||||
['UUID', uuid],
|
||||
['CODE', code],
|
||||
['TOKEN', token]
|
||||
])
|
||||
|
||||
device_menu(uuid, back_handler=back_handler)
|
156
ovos_backend_manager/microservices.py
Normal file
156
ovos_backend_manager/microservices.py
Normal file
|
@ -0,0 +1,156 @@
|
|||
import json
|
||||
|
||||
from ovos_local_backend.configuration import CONFIGURATION
|
||||
from pywebio.input import select, actions, input_group, input, TEXT, NUMBER
|
||||
from pywebio.output import put_text, put_table, popup, put_code
|
||||
|
||||
STT_CONFIGS = {
|
||||
"OpenVoiceOS (google proxy)": {"module": "ovos-stt-plugin-server", "url": "https://stt.openvoiceos.com/stt"},
|
||||
"Selene": {"module": "ovos-stt-plugin-selene", "url": "https://api.mycroft.ai"},
|
||||
"Vosk (en-us) - small": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "http://alphacephei.com/vosk/models/vosk-model-small-en-us-0.15.zip"},
|
||||
"Vosk (en-us) - large": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-en-us-aspire-0.2.zip"},
|
||||
"Vosk (fr) - small": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-small-fr-pguyot-0.3.zip"},
|
||||
"Vosk (fr) - large": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://github.com/pguyot/zamia-speech/releases/download/20190930/kaldi-generic-fr-tdnn_f-r20191016.tar.xz"},
|
||||
"Vosk (de) - small": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-small-de-0.15.zip"},
|
||||
"Vosk (de) - large": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-de-0.6.zip"},
|
||||
"Vosk (es)": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-small-es-0.3.zip"},
|
||||
"Vosk (pt)": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-small-pt-0.3.zip"},
|
||||
"Vosk (it)": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-small-it-0.4.zip"},
|
||||
"Vosk (ca)": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-small-ca-0.4.zip"},
|
||||
"Vosk (nl) - small": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-nl-spraakherkenning-0.6-lgraph.zip"},
|
||||
"Vosk (nl) - large": {"module": "ovos-stt-plugin-vosk",
|
||||
"model": "https://alphacephei.com/vosk/models/vosk-model-nl-spraakherkenning-0.6.zip"}
|
||||
}
|
||||
|
||||
|
||||
def microservices_menu(back_handler=None):
|
||||
selene = CONFIGURATION["selene"]["enabled"]
|
||||
buttons = [{'label': "View configuration", 'value': "view"},
|
||||
{'label': 'Configure STT', 'value': "stt"},
|
||||
{'label': 'Configure Secrets', 'value': "secrets"},
|
||||
{'label': 'Configure SMTP', 'value': "smtp"},
|
||||
{'label': 'Configure Wolfram Alpha', 'value': "wolfram"},
|
||||
{'label': 'Configure Weather', 'value': "weather"},
|
||||
{'label': 'Configure Geolocation', 'value': "geo"}]
|
||||
if back_handler:
|
||||
buttons.insert(0, {'label': '<- Go Back', 'value': "main"})
|
||||
|
||||
if CONFIGURATION["microservices"]["ovos_fallback"]:
|
||||
buttons.insert(-2, {'label': 'Disable OVOS microservices fallback', 'value': "ovos"})
|
||||
else:
|
||||
buttons.insert(-2, {'label': 'Enable OVOS microservices fallback', 'value': "ovos"})
|
||||
|
||||
opt = actions(label="What would you like to do?", buttons=buttons)
|
||||
if opt == "main":
|
||||
back_handler()
|
||||
return
|
||||
elif opt == "geo":
|
||||
opts = ["local"] # TODO - ovos endpoint
|
||||
if selene and CONFIGURATION["selene"]["proxy_geolocation"]:
|
||||
opts.append("selene")
|
||||
provider = select("Choose a weather provider", opts)
|
||||
# TODO - implement selection backend side, config key below not live
|
||||
CONFIGURATION["microservices"]["geolocation_provider"] = provider
|
||||
elif opt == "weather":
|
||||
opts = ["ovos"]
|
||||
if CONFIGURATION["microservices"]["owm_key"]:
|
||||
opts.append("local")
|
||||
if selene and CONFIGURATION["selene"]["proxy_weather"]:
|
||||
opts.append("selene")
|
||||
provider = select("Choose a weather provider", opts)
|
||||
CONFIGURATION["microservices"]["owm_provider"] = provider
|
||||
elif opt == "wolfram":
|
||||
opts = ["ovos"]
|
||||
if CONFIGURATION["microservices"]["wolfram_key"]:
|
||||
opts.append("local")
|
||||
if selene and CONFIGURATION["selene"]["proxy_wolfram"]:
|
||||
opts.append("selene")
|
||||
provider = select("Choose a WolframAlpha provider", opts)
|
||||
CONFIGURATION["microservices"]["wolfram_provider"] = provider
|
||||
elif opt == "ovos":
|
||||
CONFIGURATION["microservices"]["ovos_fallback"] = not CONFIGURATION["microservices"]["ovos_fallback"]
|
||||
if CONFIGURATION["microservices"]["ovos_fallback"]:
|
||||
with popup("OVOS microservices fallback enabled"):
|
||||
put_text(
|
||||
"wolfram alpha and weather requests will be proxied trough OVOS services if local keys get rate limited/become invalid/are not set")
|
||||
else:
|
||||
with popup("OVOS microservices fallback disabled"):
|
||||
put_text("please set your own wolfram alpha and weather keys")
|
||||
elif opt == "stt":
|
||||
opts = list(STT_CONFIGS.keys())
|
||||
if "Selene" in opts and not selene:
|
||||
opts.remove("Selene")
|
||||
stt = select("Choose a speech to text engine", opts)
|
||||
cfg = dict(STT_CONFIGS[stt])
|
||||
m = cfg.pop("module")
|
||||
CONFIGURATION["stt"]["module"] = m
|
||||
CONFIGURATION["stt"][m] = cfg
|
||||
with popup(f"STT set to: {stt}"):
|
||||
put_code(json.dumps(cfg, ensure_ascii=True, indent=2), "json")
|
||||
elif opt == "secrets":
|
||||
data = input_group('Secret Keys', [
|
||||
input("WolframAlpha key", value=CONFIGURATION["microservices"]["wolfram_key"],
|
||||
type=TEXT, name='wolfram'),
|
||||
input("OpenWeatherMap key", value=CONFIGURATION["microservices"]["owm_key"],
|
||||
type=TEXT, name='owm')
|
||||
])
|
||||
CONFIGURATION["microservices"]["wolfram_key"] = data["wolfram"]
|
||||
CONFIGURATION["microservices"]["owm_key"] = data["owm"]
|
||||
popup("Secrets updated!")
|
||||
elif opt == "smtp":
|
||||
# TODO - checkbox for selene proxy
|
||||
# TODO - ovos endpoint
|
||||
|
||||
if "smtp" not in CONFIGURATION["email"]:
|
||||
CONFIGURATION["email"]["smtp"] = {}
|
||||
|
||||
data = input_group('SMTP Configuration', [
|
||||
input("Username", value=CONFIGURATION["email"]["smtp"].get("username", 'user'),
|
||||
type=TEXT, name='username'),
|
||||
input("Password", value=CONFIGURATION["email"]["smtp"].get("password", '***********'),
|
||||
type=TEXT, name='password'),
|
||||
input("Host", value=CONFIGURATION["email"]["smtp"].get("host", 'smtp.mailprovider.com'),
|
||||
type=TEXT, name='host'),
|
||||
input("Port", value=CONFIGURATION["email"]["smtp"].get("port", '465'),
|
||||
type=NUMBER, name='port')
|
||||
])
|
||||
|
||||
CONFIGURATION["email"]["smtp"]["username"] = data["username"]
|
||||
CONFIGURATION["email"]["smtp"]["password"] = data["password"]
|
||||
CONFIGURATION["email"]["smtp"]["host"] = data["host"]
|
||||
CONFIGURATION["email"]["smtp"]["port"] = data["port"]
|
||||
with popup(f"SMTP configuration for: {data['host']}"):
|
||||
put_code(json.dumps(data, ensure_ascii=True, indent=2), "json")
|
||||
|
||||
if opt == "view":
|
||||
with popup("Microservices Configuration"):
|
||||
put_table([
|
||||
['STT module', CONFIGURATION["stt"]["module"]],
|
||||
['OVOS microservices fallback enabled', CONFIGURATION["microservices"]["ovos_fallback"]],
|
||||
|
||||
['WolframAlpha provider', CONFIGURATION["microservices"]["wolfram_provider"]],
|
||||
['Weather provider', CONFIGURATION["microservices"]["weather_provider"]],
|
||||
|
||||
['Selene WolframAlpha proxy enabled', selene and CONFIGURATION["selene"]["proxy_wolfram"]],
|
||||
['Selene OpenWeatherMap proxy enabled', selene and CONFIGURATION["selene"]["proxy_weather"]],
|
||||
['Selene Geolocation proxy enabled', selene and CONFIGURATION["selene"]["proxy_geolocation"]],
|
||||
['Selene Email proxy enabled', selene and CONFIGURATION["selene"]["proxy_email"]],
|
||||
|
||||
['WolframAlpha Key', CONFIGURATION["microservices"]["wolfram_key"]],
|
||||
['OpenWeatherMap Key', CONFIGURATION["microservices"]["owm_key"]]
|
||||
])
|
||||
else:
|
||||
CONFIGURATION.store()
|
||||
|
||||
microservices_menu(back_handler=back_handler)
|
114
ovos_backend_manager/selene.py
Normal file
114
ovos_backend_manager/selene.py
Normal file
|
@ -0,0 +1,114 @@
|
|||
from ovos_local_backend.configuration import CONFIGURATION
|
||||
from pywebio.input import actions
|
||||
from pywebio.output import put_table, popup
|
||||
|
||||
|
||||
def selene_menu(back_handler=None):
|
||||
if CONFIGURATION["selene"]["enabled"]:
|
||||
buttons = [{'label': "View configuration", 'value': "view"},
|
||||
{'label': "Disable Selene", 'value': "selene"}]
|
||||
|
||||
label = "Enable Proxy Pairing" if CONFIGURATION["selene"]["proxy_pairing"] else "Disable Proxy Pairing"
|
||||
buttons.insert(-2, {'label': label, 'value': "proxy"})
|
||||
label = "Enable Weather Proxy" if CONFIGURATION["selene"]["proxy_weather"] else "Disable Weather Proxy"
|
||||
buttons.insert(-2, {'label': label, 'value': "weather"})
|
||||
label = "Enable WolframAlpha Proxy" if CONFIGURATION["selene"][
|
||||
"proxy_wolfram"] else "Disable WolframAlpha Proxy"
|
||||
buttons.insert(-2, {'label': label, 'value': "wolfram"})
|
||||
label = "Enable Geolocation Proxy" if CONFIGURATION["selene"][
|
||||
"proxy_geolocation"] else "Disable Geolocation Proxy"
|
||||
buttons.insert(-2, {'label': label, 'value': "geolocation"})
|
||||
label = "Enable Email Proxy" if CONFIGURATION["selene"]["proxy_email"] else "Disable Email Proxy"
|
||||
buttons.insert(-2, {'label': label, 'value': "email"})
|
||||
label = "Enable Location Download" if CONFIGURATION["selene"][
|
||||
"download_location"] else "Disable Location Download"
|
||||
buttons.insert(-2, {'label': label, 'value': "location"})
|
||||
label = "Enable Preferences Download" if CONFIGURATION["selene"][
|
||||
"download_prefs"] else "Disable Preferences Download"
|
||||
buttons.insert(-2, {'label': label, 'value': "prefs"})
|
||||
label = "Enable SkillSettings Download" if CONFIGURATION["selene"][
|
||||
"download_settings"] else "Disable SkillSettings Download"
|
||||
buttons.insert(-2, {'label': label, 'value': "download_settings"})
|
||||
label = "Enable SkillSettings Upload" if CONFIGURATION["selene"][
|
||||
"upload_settings"] else "Disable SkillSettings Upload"
|
||||
buttons.insert(-2, {'label': label, 'value': "upload_settings"})
|
||||
label = "Enable forced 2way sync" if CONFIGURATION["selene"]["force2way"] else "Disable forced 2way sync"
|
||||
buttons.insert(-2, {'label': label, 'value': "2way"})
|
||||
label = "Enable Open Dataset Opt In" if CONFIGURATION["selene"]["opt_in"] else "Disable Open Dataset Opt In"
|
||||
buttons.insert(-2, {'label': label, 'value': "opt_in"})
|
||||
label = "Enable Metrics Upload" if CONFIGURATION["selene"]["upload_metrics"] else "Disable Metrics Upload"
|
||||
buttons.insert(-2, {'label': label, 'value': "metrics"})
|
||||
label = "Enable Wake Words Upload" if CONFIGURATION["selene"][
|
||||
"upload_wakewords"] else "Disable Wake Words Upload"
|
||||
buttons.insert(-2, {'label': label, 'value': "ww"})
|
||||
label = "Enable Utterances Upload" if CONFIGURATION["selene"][
|
||||
"upload_utterances"] else "Disable Utterances Upload"
|
||||
buttons.insert(-2, {'label': label, 'value': "stt"})
|
||||
|
||||
else:
|
||||
buttons = [{'label': "View configuration", 'value': "view"},
|
||||
{'label': "Enable Selene", 'value': "selene"}]
|
||||
|
||||
if back_handler:
|
||||
buttons.insert(0, {'label': '<- Go Back', 'value': "main"})
|
||||
|
||||
opt = actions(label="What would you like to do?", buttons=buttons)
|
||||
if opt == "main":
|
||||
back_handler()
|
||||
return
|
||||
elif opt == "geolocation":
|
||||
CONFIGURATION["selene"]["proxy_geolocation"] = not CONFIGURATION["selene"]["proxy_geolocation"]
|
||||
elif opt == "weather":
|
||||
CONFIGURATION["selene"]["proxy_weather"] = not CONFIGURATION["selene"]["proxy_weather"]
|
||||
elif opt == "wolfram":
|
||||
CONFIGURATION["selene"]["proxy_wolfram"] = not CONFIGURATION["selene"]["proxy_wolfram"]
|
||||
elif opt == "email":
|
||||
CONFIGURATION["selene"]["proxy_email"] = not CONFIGURATION["selene"]["proxy_email"]
|
||||
elif opt == "proxy":
|
||||
CONFIGURATION["selene"]["proxy_pairing"] = not CONFIGURATION["selene"]["proxy_pairing"]
|
||||
elif opt == "location":
|
||||
CONFIGURATION["selene"]["download_location"] = not CONFIGURATION["selene"]["download_location"]
|
||||
elif opt == "prefs":
|
||||
CONFIGURATION["selene"]["download_prefs"] = not CONFIGURATION["selene"]["download_prefs"]
|
||||
elif opt == "download_settings":
|
||||
CONFIGURATION["selene"]["download_settings"] = not CONFIGURATION["selene"]["download_settings"]
|
||||
elif opt == "upload_settings":
|
||||
CONFIGURATION["selene"]["upload_settings"] = not CONFIGURATION["selene"]["upload_settings"]
|
||||
elif opt == "2way":
|
||||
CONFIGURATION["selene"]["force2way"] = not CONFIGURATION["selene"]["force2way"]
|
||||
elif opt == "opt_in":
|
||||
CONFIGURATION["selene"]["opt_in"] = not CONFIGURATION["selene"]["opt_in"]
|
||||
elif opt == "selene":
|
||||
CONFIGURATION["selene"]["enabled"] = not CONFIGURATION["selene"]["enabled"]
|
||||
elif opt == "stt":
|
||||
CONFIGURATION["selene"]["upload_utterances"] = not CONFIGURATION["selene"]["upload_utterances"]
|
||||
elif opt == "ww":
|
||||
CONFIGURATION["selene"]["upload_wakewords"] = not CONFIGURATION["selene"]["upload_wakewords"]
|
||||
elif opt == "metrics":
|
||||
CONFIGURATION["selene"]["upload_metrics"] = not CONFIGURATION["selene"]["upload_metrics"]
|
||||
if opt == "view":
|
||||
with popup("Selene Proxy Configuration"):
|
||||
put_table([
|
||||
['Enabled', CONFIGURATION["selene"]["enabled"]],
|
||||
['Host', CONFIGURATION["selene"]["url"]],
|
||||
['Version', CONFIGURATION["selene"]["version"]],
|
||||
['Identity', CONFIGURATION["selene"]["identity_file"]],
|
||||
['Proxy Pairing Enabled', CONFIGURATION["selene"]["proxy_pairing"]],
|
||||
['Proxy Weather', CONFIGURATION["selene"]["proxy_weather"]],
|
||||
['Proxy WolframAlpha', CONFIGURATION["selene"]["proxy_wolfram"]],
|
||||
['Proxy Geolocation', CONFIGURATION["selene"]["proxy_geolocation"]],
|
||||
['Proxy Email', CONFIGURATION["selene"]["proxy_email"]],
|
||||
['Download Location', CONFIGURATION["selene"]["download_location"]],
|
||||
['Download Preferences', CONFIGURATION["selene"]["download_prefs"]],
|
||||
['Download Skill Settings', CONFIGURATION["selene"]["download_settings"]],
|
||||
['Upload Skill Settings', CONFIGURATION["selene"]["upload_settings"]],
|
||||
['Force 2 way Skill Settings sync', CONFIGURATION["selene"]["force2way"]],
|
||||
['OpenDataset opt in', CONFIGURATION["selene"]["opt_in"]],
|
||||
['Upload Metrics', CONFIGURATION["selene"]["upload_metrics"]],
|
||||
['Upload Wake Words', CONFIGURATION["selene"]["upload_wakewords"]],
|
||||
['Upload Utterances', CONFIGURATION["selene"]["upload_utterances"]]
|
||||
])
|
||||
else:
|
||||
CONFIGURATION.store()
|
||||
|
||||
selene_menu(back_handler=back_handler)
|
7
ovos_backend_manager/version.py
Normal file
7
ovos_backend_manager/version.py
Normal file
|
@ -0,0 +1,7 @@
|
|||
# The following lines are replaced during the release process.
|
||||
# START_VERSION_BLOCK
|
||||
VERSION_MAJOR = 0
|
||||
VERSION_MINOR = 0
|
||||
VERSION_BUILD = 1
|
||||
VERSION_ALPHA = 1
|
||||
# END_VERSION_BLOCK
|
68
setup.py
Normal file
68
setup.py
Normal file
|
@ -0,0 +1,68 @@
|
|||
import os
|
||||
from setuptools import setup
|
||||
|
||||
BASEDIR = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
|
||||
def get_version():
|
||||
""" Find the version of the package"""
|
||||
version = None
|
||||
version_file = os.path.join(BASEDIR, 'ovos_backend_manager', 'version.py')
|
||||
major, minor, build, alpha = (None, None, None, None)
|
||||
with open(version_file) as f:
|
||||
for line in f:
|
||||
if 'VERSION_MAJOR' in line:
|
||||
major = line.split('=')[1].strip()
|
||||
elif 'VERSION_MINOR' in line:
|
||||
minor = line.split('=')[1].strip()
|
||||
elif 'VERSION_BUILD' in line:
|
||||
build = line.split('=')[1].strip()
|
||||
elif 'VERSION_ALPHA' in line:
|
||||
alpha = line.split('=')[1].strip()
|
||||
|
||||
if ((major and minor and build and alpha) or
|
||||
'# END_VERSION_BLOCK' in line):
|
||||
break
|
||||
version = f"{major}.{minor}.{build}"
|
||||
if alpha and int(alpha) > 0:
|
||||
version += f"a{alpha}"
|
||||
return version
|
||||
|
||||
|
||||
def package_files(directory):
|
||||
paths = []
|
||||
for (path, directories, filenames) in os.walk(directory):
|
||||
for filename in filenames:
|
||||
paths.append(os.path.join('..', path, filename))
|
||||
return paths
|
||||
|
||||
|
||||
def required(requirements_file):
|
||||
""" Read requirements file and remove comments and empty lines. """
|
||||
with open(os.path.join(BASEDIR, requirements_file), 'r') as f:
|
||||
requirements = f.read().splitlines()
|
||||
if 'MYCROFT_LOOSE_REQUIREMENTS' in os.environ:
|
||||
print('USING LOOSE REQUIREMENTS!')
|
||||
requirements = [r.replace('==', '>=').replace('~=', '>=') for r in requirements]
|
||||
return [pkg for pkg in requirements
|
||||
if pkg.strip() and not pkg.startswith("#")]
|
||||
|
||||
|
||||
setup(
|
||||
name='ovos-backend-manager',
|
||||
version=get_version(),
|
||||
packages=['ovos_backend_manager'],
|
||||
install_requires=required("requirements.txt"),
|
||||
package_data={'': package_files('ovos_backend_manager')},
|
||||
include_package_data=True,
|
||||
url='https://github.com/OpenVoiceOS/ovos-personal-backend-ui',
|
||||
license='Apache-2.0',
|
||||
author='jarbasAI',
|
||||
author_email='jarbasai@mailfence.com',
|
||||
description='UI for OpenVoiceOS personal backend',
|
||||
entry_points={
|
||||
'console_scripts': [
|
||||
'ovos-backend-manager=ovos_backend_manager.__main__:main'
|
||||
]
|
||||
}
|
||||
)
|
Loading…
Add table
Reference in a new issue