proper package

This commit is contained in:
jarbas 2022-09-22 12:31:30 +01:00
parent f404bbbc46
commit 446deaa962
12 changed files with 1074 additions and 733 deletions

203
LICENSE Normal file
View 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.

View file

@ -4,11 +4,19 @@ a simple UI for [OVOS-local-backend](https://github.com/OpenVoiceOS/OVOS-local-b
![](./screenshots/demo.gif)
## 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
View file

@ -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)

View 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)

View 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()

View 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)

View 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)

View 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)

View 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)

View 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)

View 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
View 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'
]
}
)