Compare commits
537 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 754bd7380f | |||
| d32cda3663 | |||
| c8d2e8c985 | |||
| 2a15434919 | |||
| b2c375861b | |||
| ae6fec335d | |||
| 16302ec6e4 | |||
| 4d03e1bb4e | |||
| 69ce669b4c | |||
| c512b422e7 | |||
| 2ad6541c3e | |||
| 379326d7c1 | |||
| 1359f5d210 | |||
| 64858ec6fe | |||
| 166175192d | |||
| 97de1d1c3f | |||
| 563792bd0a | |||
| 076db4c3eb | |||
| 02e1de8ffa | |||
| cc01bd18b2 | |||
| 2478adbaa2 | |||
| 45b177d0fb | |||
| c0deafcd69 | |||
| eea6b0392d | |||
| 9845612c68 | |||
| d46ec1d3d5 | |||
| f1a65f53bc | |||
| 1410b72362 | |||
| e9245b522f | |||
| 636eb0f139 | |||
| 8645ef01fd | |||
| 18867bde89 | |||
| d061a6c4bf | |||
| e2ec76275a | |||
| 767bace201 | |||
| e70d0a0029 | |||
| 595fe78679 | |||
| 69cb6a610f | |||
| 10a03d90e3 | |||
| 251950384e | |||
| 8c91d245e8 | |||
| df343ce94f | |||
| 7c73f435af | |||
| b5ef7242af | |||
| 16e1f7a34a | |||
| e42a54d593 | |||
| cd3bcd5d6e | |||
| 2ea6237b1f | |||
| 9782526c42 | |||
| 3bf7150387 | |||
| c00c85fd4f | |||
| a76d05f68a | |||
| f17a4d3a2a | |||
| 850ea46ec6 | |||
| a540307241 | |||
| e440e4f715 | |||
| d1fe81ddc6 | |||
| 3648fef5fb | |||
| 1f9137bb20 | |||
| 2e6abef507 | |||
| a55fbcd532 | |||
| 924317c614 | |||
| 0c44916aa2 | |||
| 2a0cc0644e | |||
| 4f5b385fd7 | |||
| 30e405bbe3 | |||
| 8c105a043d | |||
| 99b5e7ab6d | |||
| b9dbc5012c | |||
| 0b16eebfbd | |||
| c078505846 | |||
| 421232a6dc | |||
| c62368ddad | |||
| f956e1abc6 | |||
| dc88ac244d | |||
| 4cd01a4baf | |||
| 06cda4a1f0 | |||
| dc7615723d | |||
| 14275be5f7 | |||
| 158df2f0f1 | |||
| 6bed516f89 | |||
| 7e72928ad6 | |||
| 4a7e77141e | |||
| 25ab60e5a2 | |||
| f8e6012cbe | |||
| 1c5d2874b0 | |||
| 3449c14d47 | |||
| 51f57e3255 | |||
| 7fd4be1248 | |||
| 3b77d64d4f | |||
| ab7e2a9b10 | |||
| b2e4ebf1be | |||
| 3c9a210081 | |||
| 7a9068283b | |||
| 66dc0aab0f | |||
| 91e48af263 | |||
| 3a230633d8 | |||
| 6c3a03d16a | |||
| 8a60b3d0d3 | |||
| cb3d3bf5a8 | |||
| f3257ad7b0 | |||
| cb29848f45 | |||
| ebaa3c4f9b | |||
| de17719537 | |||
| 47d5a48ac9 | |||
| e8ceed62eb | |||
| bbc1f8a79c | |||
| 289efb5610 | |||
| 05a4b2c773 | |||
| a32ad86342 | |||
| a2eacc3930 | |||
| a8c079fd5c | |||
| b389bdd8c6 | |||
| 750d1de8b4 | |||
| d295105fa6 | |||
| e2899c8d9a | |||
| 7f9b60a6cd | |||
| 9e77953976 | |||
| cb6462b749 | |||
| 07a3b1701d | |||
| f6300a3fb8 | |||
| 46299ef4eb | |||
| 259dc53213 | |||
| 51152a0fe3 | |||
| 01305af669 | |||
| 5539130dd9 | |||
| efd1061f30 | |||
| f6a501d4e1 | |||
| 839c7bfc3c | |||
| d3ba85cb36 | |||
| 623039415a | |||
| 7a75c985e0 | |||
| 0f2a579a66 | |||
| 63573083fa | |||
| 34aa188643 | |||
| 76dfe72f5f | |||
| 7a31158497 | |||
| 96145bb2f7 | |||
| 17cb808aab | |||
| b757d2d4ae | |||
| ce001c2e65 | |||
| a3b8c40714 | |||
| 0283878a90 | |||
| 3ed19bd6d0 | |||
| c163f5971c | |||
| 6a01e1e743 | |||
| b06eab9107 | |||
| 12ce47b937 | |||
| c8fc0068d1 | |||
| e32bf370db | |||
| 9ef4f234b8 | |||
| 6d2292f508 | |||
| b897ae160f | |||
| a51d92bf08 | |||
| 6d495cf43c | |||
| d6933a9e35 | |||
| 127a10f8ed | |||
| ef223a2901 | |||
| 9a06c64a5d | |||
| ad5edb5ad5 | |||
| ee0b080522 | |||
| be04b10645 | |||
| ab62a65e57 | |||
| 99ac5f18c8 | |||
| 87a7077854 | |||
| af2a124165 | |||
| 8e55c481d4 | |||
| d7b814761f | |||
| 4dde49e545 | |||
| 34b29d0e61 | |||
| 954126315f | |||
| e240a2db3c | |||
| 4abb68c1b3 | |||
| 3ea1a3666b | |||
| d3e0810298 | |||
| ef6d46cf8e | |||
| e65ecd5b67 | |||
| 5946a00427 | |||
| 6b239ae38b | |||
| 224158812f | |||
| ee9794cd9f | |||
| ad497d36c3 | |||
| 5eb62ddcf5 | |||
| ec819e1765 | |||
| 2c2f49290a | |||
| f936652007 | |||
| 950985ca86 | |||
| 8147d8423b | |||
| 3f556494c2 | |||
| 426c816ca4 | |||
| dd0eab51b6 | |||
| 07e253f663 | |||
| de08807f1a | |||
| 2cb030f47b | |||
| 673cf0426f | |||
| e89b5b0ea3 | |||
| 6b76e6a102 | |||
| 1625aaf3cd | |||
| b65cfb70d6 | |||
| 89a6c04a1e | |||
| e42655a5ff | |||
| c9678fe34f | |||
| 8349e41205 | |||
| 759e67ac40 | |||
| fc1fa56eab | |||
| 8f5bd3a9a5 | |||
| a836a7cc8c | |||
| 2df22770e0 | |||
| 9b839d3e66 | |||
| 2e3796642d | |||
| b027905ed0 | |||
| 4c9dfe667b | |||
| ed215feb81 | |||
| cd82a3a11b | |||
| b7a4f69565 | |||
| 7b8a324658 | |||
| 59cfded2bb | |||
| 8dc8c05d7d | |||
| 0e4ec4fb97 | |||
| bb00677568 | |||
| 308b63f576 | |||
| 40819e8e17 | |||
| 0fba50905f | |||
| 419bfe3d78 | |||
| 1847248f12 | |||
| eed4a9597c | |||
| c3452d2170 | |||
| 0a14567776 | |||
| 71573b78d1 | |||
| 722dba875b | |||
| 593419639e | |||
| 8b2f06e305 | |||
| 12e4b7bccf | |||
| 63a6c2d161 | |||
| f0e898e3c0 | |||
| 10bb4af4dc | |||
| 1af3fc2011 | |||
| 781cb072a3 | |||
| 73fe21f02e | |||
| 0947bd7877 | |||
| 07d2a41ecb | |||
| 9a66b677e0 | |||
| fe580cfb92 | |||
| 075f4458a0 | |||
| 094b1218e6 | |||
| b16475766a | |||
| e5fdeba50b | |||
| 12b0fe6525 | |||
| 302e1e11e0 | |||
| 8d5bca0d2b | |||
| 994cca7bf4 | |||
| a03afa78be | |||
| a1f93a6922 | |||
| 2678f6f04a | |||
| 03acb35e84 | |||
| 337854ab02 | |||
| 19c569b76a | |||
| 5b4cd9da81 | |||
| 40069ab5f0 | |||
| 8c89cbf43c | |||
| 5061cac591 | |||
| f660506d98 | |||
| 25f81d3c3c | |||
| d261967920 | |||
| 907fb1a129 | |||
| 7d63108e8d | |||
| 274d7d52fd | |||
| d815ab8bff | |||
| 35da3a388a | |||
| a29296c612 | |||
| b93180a822 | |||
| 9fa0c2e1ba | |||
| 2115054de6 | |||
| f167f64cfb | |||
| abc6b21df2 | |||
| bd66bb5cb7 | |||
| c49f931da4 | |||
| 070f0e1469 | |||
| bf4806c987 | |||
| 37e5ca0e87 | |||
| a6011e3cc6 | |||
| 44882129a7 | |||
| 8282c37a5a | |||
| 26ff4520c8 | |||
| e2f0df2cb6 | |||
| 7259ceda59 | |||
| 7f99372130 | |||
| 0cbd438a34 | |||
| 71b30a7f97 | |||
| bfdc60fa65 | |||
| 99af936803 | |||
| d41e960373 | |||
| ae04ba369c | |||
| afff309d43 | |||
| 16c25c9036 | |||
| cd551197ea | |||
| f6077d58f1 | |||
| eb026cb74a | |||
| 7d6698df17 | |||
| a215fc8770 | |||
| a8e3fb8b87 | |||
| 6820501da6 | |||
| b51bf4b1c2 | |||
| 5369da1734 | |||
| d5d8d56124 | |||
| fbcc089e2b | |||
| 76dacb4f5e | |||
| 24ec0f0927 | |||
| b8aac89c4d | |||
| 72a4f787a3 | |||
| 137032b1c0 | |||
| 5011d906e7 | |||
| 35d171e421 | |||
| 20e7a98241 | |||
| abf811f2e4 | |||
| 4d7c49e1ee | |||
| e182da0048 | |||
| f81255aa20 | |||
| 7aaca4dfc8 | |||
| 4a74ee030a | |||
| 7c76e538ab | |||
| 87ed70d313 | |||
| 98bb70bee6 | |||
| a77f0e2473 | |||
| 03b128a544 | |||
| 9c6a60f92c | |||
| 311923255e | |||
| cb77b4fc95 | |||
| 4883d7723b | |||
| eb72a61bad | |||
| 6dfeb6480f | |||
| 51f9a29679 | |||
| d51bf36566 | |||
| 9c9f944350 | |||
| 7b557da549 | |||
| 849749b900 | |||
| 528da32d03 | |||
| 9ef7476267 | |||
| 0f897942fa | |||
| 885f3738fc | |||
| ab0d88accf | |||
| 1146322196 | |||
| 157e28a063 | |||
| c8bc9ecbb0 | |||
| 18ba094647 | |||
| 31e44cde37 | |||
| 248dbb84f0 | |||
| 609fb676e4 | |||
| 5d866664b2 | |||
| ba09ef10b1 | |||
| 19ce6eb9c2 | |||
| 934689ecf7 | |||
| 2f56a14d68 | |||
| cc05ce3d76 | |||
| 5959b43e0a | |||
| b9a6ee08fe | |||
| b1739bbe6b | |||
| cad6d7f725 | |||
| a08ddb2e26 | |||
| c7aa84f9c4 | |||
| ec10030951 | |||
| b298667e3d | |||
| 69e90c3b5f | |||
| 2ada7f2efa | |||
| 28c15885fd | |||
| 194a9b1bbb | |||
| 4ce9676319 | |||
| fb75b40fb6 | |||
| d973ab4318 | |||
| f8b08f38b1 | |||
| b3b10e326f | |||
| efe865a4c5 | |||
| a37ac96411 | |||
| 8a16a3d97e | |||
| 3d9d9496b7 | |||
| 862112ed5f | |||
| ea76739d88 | |||
| c53feba8f5 | |||
| 62d511f967 | |||
| 664359158e | |||
| 8ecde83b89 | |||
| 420f87daca | |||
| c722050aec | |||
| c434b550a5 | |||
| 06a2adf62b | |||
| bb0cf69659 | |||
| e50fadd142 | |||
| 327cbff184 | |||
| 62853a340f | |||
| 86852a8cac | |||
| efc94a2ac3 | |||
| fd4f429b86 | |||
| f5404920fa | |||
| a0d8c7251c | |||
| 303ebea3f3 | |||
| c38012afcf | |||
| becac3b0f6 | |||
| 0245712e24 | |||
| 4b003a852e | |||
| ddf389ffd1 | |||
| 1d59b0a774 | |||
| 74582899a2 | |||
| 8a91dcb85d | |||
| 8fbc4bb23f | |||
| 61d31d9b52 | |||
| a81e536fd5 | |||
| 25fdaeeab2 | |||
| c4885cb324 | |||
| 71e0f1933c | |||
| 65246dc7cd | |||
| 32fd041495 | |||
| 6c292994fd | |||
| 6e8c326a9c | |||
| a4e9b1e1cc | |||
| 29d7f16b2c | |||
| e3c0a180af | |||
| 360c4d4f6f | |||
| 1a580d1051 | |||
| f13df6ab76 | |||
| 239472b61e | |||
| 83c501dc8d | |||
| 67d07ed7e6 | |||
| 18a728ff4b | |||
| b77e3a99e9 | |||
| daba510755 | |||
| dcc04de6bc | |||
| 38693e42ef | |||
| ceaff26d2d | |||
| db373b66a6 | |||
| fb03359400 | |||
| d8e40c9cce | |||
| ecca030ab9 | |||
| 35322b39e3 | |||
| be079b87c8 | |||
| 2a7b2f45d6 | |||
| 2a48bc6343 | |||
| 5acd22a885 | |||
| 632a048c6b | |||
| e1e4910399 | |||
| 5bd67be2e5 | |||
| 482f7bfbb8 | |||
| 2d6f16a311 | |||
| 3f8027af2a | |||
| da063b73bc | |||
| 3f97a12153 | |||
| 6bee56580b | |||
| ad1708abbb | |||
| bd1671c440 | |||
| e527110b9c | |||
| 0cf39c181f | |||
| c0886c086f | |||
| e459c1011c | |||
| 9928509171 | |||
| 16444ae79f | |||
| 75a351e77d | |||
| 5c02e906fa | |||
| 03b0d08ae0 | |||
| 6dbdfe7ba5 | |||
| 8ab9c2e4df | |||
| c912b637ee | |||
| 4df23cfe45 | |||
| 65592b9af5 | |||
| 8f9937ddee | |||
| 5042bcc95d | |||
| cc6503e6f7 | |||
| 8de9d84f1f | |||
| 29c582c89e | |||
| bc53a36cd8 | |||
| 84cdc7fa60 | |||
| 1dc0451f0f | |||
| 1beebcccd2 | |||
| b6b42c422c | |||
| bda75915b2 | |||
| b099d53871 | |||
| c6ad1968e7 | |||
| bc37bd1687 | |||
| 7161cd5996 | |||
| 23b7d5b49e | |||
| 599f9561e4 | |||
| bd45220c66 | |||
| 53757651c8 | |||
| bd1bde0814 | |||
| ad044814a5 | |||
| aede3cbf52 | |||
| 79115d1b25 | |||
| 404a1d5b3f | |||
| 8a191b9c91 | |||
| 56a9a796bd | |||
| 5bc0a7f97b | |||
| 7fe7b2feaa | |||
| ee0134598c | |||
| b19e5ad0cb | |||
| de442741e5 | |||
| f43e17aad4 | |||
| ddbe4f0fd1 | |||
| f17a601d8e | |||
| 5bace9545a | |||
| f170a1dd8e | |||
| 91eac5c875 | |||
| 518889b459 | |||
| cc8a32d17d | |||
| a00b01c041 | |||
| 7db5c064d9 | |||
| ed8390a36c | |||
| 1d45c2e715 | |||
| 784f86f6f3 | |||
| 9d865291f0 | |||
| bc7dd0f836 | |||
| 8d64ad9650 | |||
| b3fdde9d83 | |||
| ea6fc4108a | |||
| 40958f2fb4 | |||
| 8e0d18a596 | |||
| 861d773cdd | |||
| 6bd9f0ef9d | |||
| d69157439c | |||
| 1a3d4f2204 | |||
| b59f6a7264 | |||
| bf902fd2d9 | |||
| a8272e1bf9 | |||
| b96070085b | |||
| 48688dc6e6 | |||
| 9dd9b010ed | |||
| 0888ae5033 | |||
| 2d7be3348a | |||
| bee3fbb9e2 | |||
| 55db68400d | |||
| 7d2432c56c | |||
| 48731e8ddf | |||
| 4d0a97f9c2 | |||
| 133310bd7d | |||
| 69bf9cebb3 | |||
| 91e39a0a37 | |||
| 586ca11106 | |||
| 20b967b4ea | |||
| dd9d024975 | |||
| 8d1ab6973a |
+9
-2
@@ -1,2 +1,9 @@
|
||||
node_modules/
|
||||
bridges/python/src/.venv/*
|
||||
__pycache__/
|
||||
**/dist/*
|
||||
**/build/
|
||||
**/node_modules/
|
||||
**/tmp/*
|
||||
**/src/.venv/*
|
||||
logs/*
|
||||
*.pyc
|
||||
.DS_Store
|
||||
|
||||
@@ -12,5 +12,4 @@ end_of_line = lf
|
||||
insert_final_newline = true
|
||||
|
||||
[*.py]
|
||||
indent_style = tab
|
||||
indent_size = 4
|
||||
|
||||
+2
-2
@@ -31,8 +31,8 @@ LEON_HTTP_API_KEY=
|
||||
# Language used for the HTTP API
|
||||
LEON_HTTP_API_LANG=en-US
|
||||
|
||||
# Enable/disable collaborative logger
|
||||
LEON_LOGGER=true
|
||||
# Enable/disable telemetry
|
||||
LEON_TELEMETRY=true
|
||||
|
||||
# Python TCP server
|
||||
LEON_PY_TCP_SERVER_HOST=0.0.0.0
|
||||
|
||||
+22
-1
@@ -26,14 +26,29 @@
|
||||
"plugins": ["@typescript-eslint", "unicorn", "import"],
|
||||
"ignorePatterns": "*.spec.js",
|
||||
"rules": {
|
||||
"@typescript-eslint/no-non-null-assertion": ["off"],
|
||||
"no-async-promise-executor": ["off"],
|
||||
"no-underscore-dangle": ["error", { "allowAfterThis": true }],
|
||||
"prefer-destructuring": ["error"],
|
||||
"comma-dangle": ["error", "never"],
|
||||
"semi": ["error", "never"],
|
||||
"object-curly-spacing": ["error", "always"],
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"unicorn/prefer-node-protocol": "error",
|
||||
"@typescript-eslint/member-delimiter-style": [
|
||||
"error",
|
||||
{
|
||||
"multiline": {
|
||||
"delimiter": "none",
|
||||
"requireLast": true
|
||||
},
|
||||
"singleline": {
|
||||
"delimiter": "comma",
|
||||
"requireLast": false
|
||||
}
|
||||
}
|
||||
],
|
||||
"@typescript-eslint/explicit-function-return-type": "off",
|
||||
"@typescript-eslint/consistent-type-definitions": "error",
|
||||
"import/no-named-as-default": "off",
|
||||
"import/no-named-as-default-member": "off",
|
||||
"import/order": [
|
||||
@@ -52,6 +67,12 @@
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["skills/**/*.ts"],
|
||||
"rules": {
|
||||
"import/order": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts"],
|
||||
"rules": {
|
||||
|
||||
+78
-4
@@ -51,7 +51,7 @@ Choose the setup method you want to go for.
|
||||
|
||||
### Single-Click
|
||||
|
||||
Gitpod will automatically setup an environment and run an instance for you.
|
||||
Gitpod will automatically set up an environment and run an instance for you.
|
||||
|
||||
[](https://gitpod.io/#https://github.com/leon-ai/leon)
|
||||
|
||||
@@ -123,11 +123,14 @@ Types define which kind of changes you made to the project.
|
||||
|
||||
Scopes define high-level nodes of Leon.
|
||||
|
||||
- web app
|
||||
- bridge/python
|
||||
- bridge/nodejs
|
||||
- docker
|
||||
- hotword
|
||||
- scripts
|
||||
- server
|
||||
- tcp server
|
||||
- python bridge
|
||||
- hotword
|
||||
- web app
|
||||
- skill/skill_name
|
||||
|
||||
### Examples
|
||||
@@ -149,6 +152,77 @@ By sponsoring the project you make the project sustainable and faster to develop
|
||||
|
||||
The focus is not only limited to the activity you see on GitHub but also a lot of thinking about the direction of the project. Which is naturally related to the overall design, architecture, vision, learning process and so on...
|
||||
|
||||
## Contributing to the Python Bridge or TCP HttpServer
|
||||
|
||||
Leon makes use of two binaries, the Python bridge and the TCP server. These binaries are compiled from Python sources.
|
||||
|
||||
The Python bridge is used to communicate between the core and skills made with Python.
|
||||
|
||||
The TCP server is used to communicate between the core and third-party nodes, such as spaCy.
|
||||
|
||||
### Set Up the Python Environment
|
||||
|
||||
To contribute to these parts, you need to set up a Python environment running with a specific Python version and a specific Pipenv version.
|
||||
|
||||
It is recommended to use Pyenv to manage your Python versions.
|
||||
|
||||
If you are on GNU/Linux Ubuntu, you can run the following to install Pyenv, otherwise, please refer to the [Pyenv documentation to install it](https://github.com/pyenv/pyenv#installation):
|
||||
|
||||
```bash
|
||||
# Update registry
|
||||
sudo apt-get update
|
||||
|
||||
# Install Pyenv deps
|
||||
sudo apt-get install make build-essential libssl-dev zlib1g-dev \
|
||||
libbz2-dev libreadline-dev libsqlite3-dev wget curl llvm \
|
||||
libncursesw5-dev xz-utils tk-dev libxml2-dev libxmlsec1-dev libffi-dev liblzma-dev
|
||||
|
||||
# Install Pyenv
|
||||
curl https://pyenv.run | bash
|
||||
|
||||
# Add output lines to .bashrc
|
||||
|
||||
# Restart shell
|
||||
exec "$SHELL"
|
||||
```
|
||||
|
||||
Once Pyenv installed, run:
|
||||
|
||||
```bash
|
||||
# Install Python
|
||||
pyenv install 3.9.10 --force
|
||||
pyenv global 3.9.10
|
||||
|
||||
# Install Pipenv
|
||||
pip install pipenv==2022.7.24
|
||||
```
|
||||
|
||||
Your Python environment should be ready now. So now you can set up the respective environments according to what you are going to contribute to and build them:
|
||||
|
||||
```bash
|
||||
# Set up the Python bridge environment
|
||||
npm run setup:python-bridge
|
||||
|
||||
# Set up the TCP server environment
|
||||
npm run setup:tcp-server
|
||||
# If you are in China, you can run this to download models faster:
|
||||
npm run setup:tcp-server cn
|
||||
|
||||
# Once your code changes are done, you can build via:
|
||||
|
||||
# Build the Python bridge
|
||||
npm run build:python-bridge
|
||||
|
||||
# Build the TCP server
|
||||
npm run build:tcp-server
|
||||
|
||||
# Run the Python bridge
|
||||
./bridges/python/dist/{OS-CPU_ARCH}/leon-python-bridge server/src/intent-object.sample.json
|
||||
|
||||
# Run the TCP server
|
||||
./tcp_server/dist/{OS-CPU_ARCH}/leon-tcp-server en
|
||||
```
|
||||
|
||||
## Spread the Word
|
||||
|
||||
Use [#LeonAI](<https://twitter.com/search?f=live&q=%23LeonAI%20(from%3Agrenlouis%20OR%20from%3Alouistiti_fr)&src=typed_query>) if you tweet about Leon and/or mention [@grenlouis](https://twitter.com/grenlouis).
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
name: Pre-release Node.js bridge
|
||||
|
||||
on: workflow_dispatch
|
||||
|
||||
jobs:
|
||||
build:
|
||||
name: Build
|
||||
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Set Node.js bridge version
|
||||
working-directory: bridges/nodejs/src
|
||||
run: |
|
||||
echo "NODEJS_BRIDGE_VERSION=$(node --require fs --eval "const fs = require('node:fs'); const [, VERSION] = fs.readFileSync('version.ts', 'utf8').split(\"'\"); console.log(VERSION)")" >> $GITHUB_ENV
|
||||
|
||||
- name: Display Node.js bridge version
|
||||
run: |
|
||||
echo "Node.js bridge version: ${{ env.NODEJS_BRIDGE_VERSION }}"
|
||||
|
||||
- name: Install core
|
||||
run: npm install
|
||||
|
||||
- name: Build Node.js bridge
|
||||
run: npm run build:nodejs-bridge
|
||||
|
||||
- name: Upload Node.js bridge
|
||||
uses: actions/upload-artifact@v3
|
||||
with:
|
||||
path: bridges/nodejs/dist/*.zip
|
||||
|
||||
draft-release:
|
||||
name: Draft-release
|
||||
needs: [build]
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- name: Install Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
|
||||
- name: Set Node.js bridge version
|
||||
working-directory: bridges/nodejs/src
|
||||
run: |
|
||||
echo "NODEJS_BRIDGE_VERSION=$(node --require fs --eval "const fs = require('node:fs'); const [, VERSION] = fs.readFileSync('version.ts', 'utf8').split(\"'\"); console.log(VERSION)")" >> $GITHUB_ENV
|
||||
|
||||
- name: Download Node.js bridge
|
||||
uses: actions/download-artifact@v3
|
||||
with:
|
||||
path: bridges/nodejs/dist
|
||||
|
||||
- uses: marvinpinto/action-automatic-releases@latest
|
||||
with:
|
||||
repo_token: ${{ secrets.GITHUB_TOKEN }}
|
||||
automatic_release_tag: nodejs-bridge_v${{ env.NODEJS_BRIDGE_VERSION }}
|
||||
draft: true
|
||||
prerelease: false
|
||||
title: Node.js Bridge ${{ env.NODEJS_BRIDGE_VERSION }}
|
||||
files: bridges/nodejs/dist/artifact/*.zip
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
@@ -33,7 +33,6 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: npm
|
||||
|
||||
- name: Set Python bridge version
|
||||
working-directory: bridges/python/src
|
||||
@@ -45,7 +44,7 @@ jobs:
|
||||
echo "Python bridge version: ${{ env.PYTHON_BRIDGE_VERSION }}"
|
||||
|
||||
- name: Install core
|
||||
run: npm ci
|
||||
run: npm install
|
||||
|
||||
- name: Set up Python bridge
|
||||
run: npm run setup:python-bridge
|
||||
@@ -61,7 +60,7 @@ jobs:
|
||||
draft-release:
|
||||
name: Draft-release
|
||||
needs: [build]
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
|
||||
@@ -13,7 +13,7 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-latest, macos-latest, windows-latest]
|
||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
@@ -33,7 +33,6 @@ jobs:
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
node-version: lts/*
|
||||
cache: npm
|
||||
|
||||
- name: Set TCP server version
|
||||
working-directory: tcp_server/src
|
||||
@@ -45,7 +44,7 @@ jobs:
|
||||
echo "TCP server version: ${{ env.TCP_SERVER_VERSION }}"
|
||||
|
||||
- name: Install core
|
||||
run: npm ci
|
||||
run: npm install
|
||||
|
||||
- name: Set up TCP server
|
||||
run: npm run setup:tcp-server
|
||||
@@ -61,7 +60,7 @@ jobs:
|
||||
draft-release:
|
||||
name: Draft-release
|
||||
needs: [build]
|
||||
runs-on: ubuntu-latest
|
||||
runs-on: ubuntu-20.04
|
||||
|
||||
steps:
|
||||
- name: Clone repository
|
||||
|
||||
+6
-4
@@ -8,27 +8,29 @@ __pycache__/
|
||||
test/coverage/
|
||||
**/tmp/*
|
||||
**/src/.venv/*
|
||||
downloads/*
|
||||
logs/*
|
||||
core/config/**/*.json
|
||||
bin/coqui/*
|
||||
bin/flite/*
|
||||
|
||||
package-lock.json
|
||||
*.pyc
|
||||
.DS_Store
|
||||
*.sublime-workspace
|
||||
npm-debug.log
|
||||
debug.log
|
||||
.env
|
||||
.last-skill-npm-install
|
||||
leon.json
|
||||
bridges/python/src/Pipfile.lock
|
||||
tcp_server/src/Pipfile.lock
|
||||
!tcp_server/**/.gitkeep
|
||||
!bridges/python/**/.gitkeep
|
||||
!bridges/nodejs/**/.gitkeep
|
||||
!**/*.sample*
|
||||
packages/**/config/config.json
|
||||
skills/**/src/config.json
|
||||
packages/**/data/db/*.json
|
||||
skills/**/src/settings.json
|
||||
skills/**/memory/*.json
|
||||
core/data/models/*.nlp
|
||||
package.json.backup
|
||||
.python-version
|
||||
schemas/**/*.json
|
||||
|
||||
@@ -1,3 +1,7 @@
|
||||
# [1.0.0-beta.8](https://github.com/leon-ai/leon/compare/v1.0.0-beta.7...v1.0.0-beta.8) (2023-05-01) / Binaries and TypeScript Rewrite
|
||||
|
||||
_Please refer to [our latest blog post](https://blog.getleon.ai/binaries-and-typescript-rewrite-1-0-0-beta-8/) for more information on the new release of our dear Leon._
|
||||
|
||||
# [1.0.0-beta.7](https://github.com/leon-ai/leon/compare/v1.0.0-beta.6...v1.0.0-beta.7) (2022-08-24) / A Much Better NLP
|
||||
|
||||
_Please [read this blog post](https://blog.getleon.ai/a-much-better-nlp-and-future-1-0-0-beta-7/) to know more about all the new features and the exciting future of Leon._
|
||||
|
||||
+22
-31
@@ -1,4 +1,4 @@
|
||||
FROM ubuntu:latest
|
||||
FROM ubuntu:20.04
|
||||
ENV IS_DOCKER true
|
||||
|
||||
# Replace shell with bash so we can source files
|
||||
@@ -15,18 +15,23 @@ RUN apt-get update && apt-get install --yes -q --no-install-recommends \
|
||||
curl \
|
||||
git \
|
||||
wget \
|
||||
libssl-dev \
|
||||
zlib1g-dev \
|
||||
libbz2-dev \
|
||||
libreadline-dev \
|
||||
libsqlite3-dev \
|
||||
llvm \
|
||||
libncurses5-dev \
|
||||
xz-utils \
|
||||
tk-dev libxml2-dev \
|
||||
libxmlsec1-dev \
|
||||
libffi-dev \
|
||||
liblzma-dev
|
||||
libssl-dev \
|
||||
openssl \
|
||||
libz-dev \
|
||||
zlib1g-dev \
|
||||
libbz2-dev \
|
||||
libreadline-dev \
|
||||
libsqlite3-dev \
|
||||
llvm \
|
||||
libncurses5-dev \
|
||||
xz-utils \
|
||||
tk-dev libxml2-dev \
|
||||
libxmlsec1-dev \
|
||||
libffi-dev \
|
||||
liblzma-dev \
|
||||
libgdbm-dev \
|
||||
libnss3-dev \
|
||||
libc6-dev
|
||||
|
||||
# Run the container as an unprivileged user
|
||||
RUN groupadd docker && useradd -g docker -s /bin/bash -m docker
|
||||
@@ -34,28 +39,14 @@ USER docker
|
||||
WORKDIR /home/docker
|
||||
|
||||
# Install Node.js with nvm
|
||||
ENV NVM_DIR /home/docker/nvm
|
||||
ENV NODE_VERSION v16.17.0
|
||||
ENV NVM_DIR /home/docker/.nvm
|
||||
ENV NODE_VERSION v16.18.0
|
||||
|
||||
RUN curl -o- https://raw.githubusercontent.com/creationix/nvm/v0.33.1/install.sh | bash
|
||||
RUN curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.39.2/install.sh | bash
|
||||
RUN /bin/bash -c "source $NVM_DIR/nvm.sh && nvm install $NODE_VERSION && nvm use --delete-prefix $NODE_VERSION"
|
||||
|
||||
ENV NODE_PATH $NVM_DIR/versions/node/$NODE_VERSION/lib/node_modules
|
||||
ENV PATH $NVM_DIR/versions/node/$NODE_VERSION/bin:$PATH
|
||||
|
||||
# Install Python with pyenv
|
||||
RUN git clone --depth=1 https://github.com/pyenv/pyenv.git .pyenv
|
||||
ENV PYENV_ROOT="/home/docker/.pyenv"
|
||||
ENV PATH="${PYENV_ROOT}/shims:${PYENV_ROOT}/bin:${PATH}"
|
||||
|
||||
ENV PYTHON_VERSION=3.9.10
|
||||
RUN pyenv install ${PYTHON_VERSION}
|
||||
RUN pyenv global ${PYTHON_VERSION}
|
||||
|
||||
# Install Pipenv
|
||||
ENV PYTHON_BIN_PATH /home/docker/.local/bin
|
||||
ENV PATH="${PYTHON_BIN_PATH}:${PATH}"
|
||||
RUN python -m pip install --user --force-reinstall pipenv virtualenv
|
||||
ENV PATH $NVM_DIR/versions/node/$NODE_VERSION/bin:$PATH
|
||||
|
||||
# Install Leon
|
||||
WORKDIR /home/docker/leon
|
||||
|
||||
@@ -28,14 +28,47 @@ _<p align="center">Your open-source personal assistant.</p>_
|
||||
<a href="https://blog.getleon.ai/the-story-behind-leon/">Story</a>
|
||||
</p>
|
||||
|
||||
<br>
|
||||
<h2 align="center">📢 Notice 📢</h2>
|
||||
<p align="center">
|
||||
<a href="https://blog.getleon.ai/a-much-better-nlp-and-future-1-0-0-beta-7/"><img width="400" src="https://blog.getleon.ai/static/a62ac28a01cb6898e299dced40875a68/c1b63/beta-7.png" /></a>
|
||||
<br>
|
||||
Many exciting things are coming up, hence no new documentation and test are going to be written until the official release of Leon. Feel free to <a href="https://discord.gg/MNQqqKg"><b>join us on Discord</b></a> to know more and to read the <a href="https://blog.getleon.ai/a-much-better-nlp-and-future-1-0-0-beta-7/"><b>"A Much Better NLP and Future" blog post</b></a>.
|
||||
</p>
|
||||
<br><br>
|
||||
---
|
||||
|
||||
## Current State
|
||||
|
||||
### Why is there a small amount of contributors?
|
||||
|
||||
I'm taking a lot of time to work on the new core of Leon due to personal reasons. I can only work on it after work and on weekends. Hence, **I'm blocking any potential contribution as the whole core of Leon is coming with many breaking changes**. Many of you are willing to contribute in Leon (create new skills, help to improve the core, translations and so on...), a big thanks to every one of you!
|
||||
|
||||
While I would love to devote more time to Leon, I'm currently unable to do so because I have bills to pay. I have some ideas about how to monetize Leon in the future (Leon's core will always remain open source), but before to get there there is still a long way to go.
|
||||
|
||||
Until then, any financial support by [sponsoring Leon](http://sponsor.getleon.ai) is much appreciated 🙂
|
||||
|
||||
### How about large language models and Leon?
|
||||
|
||||
Since AI gained in popularity and large language models are getting more and more traction, many of you joined our community. A huge welcome to all of you! 🤗
|
||||
|
||||
At the moment, Leon's NLU will remain intents first with his own model without relying on an LLM. It is important that Leon can run 100% offline and I'm confident that with the downsizing techniques such as quantization Leon will sooner or later work with LLMs at his core and still be able to run on edge.
|
||||
|
||||
Here is how LLMs may help Leon in the future:
|
||||
|
||||
- Intent fallback: when an utterance cannot match an intent, then rely on an LLM to provide results.
|
||||
- New named entity recognition engine: provide a better solution to extract entities from utterances such as fruits, numbers, cities, durations, persons, etc.
|
||||
- Skill features: let skills leverage LLMs to provide out-of-the-box NLP features such as summarization, knowledge base, translation, sentiment analysis and so on...
|
||||
- Skill building: LLMs can help to develop skills such as paraphrasing utterance samples, translate answers, convert code from our Python bridge to the upcoming JavaScript bridge and vice versa, etc.
|
||||
- More...
|
||||
|
||||
### What's Next?
|
||||
|
||||
Once the new core released, we'll work on the community aspect of Leon. For example, better organize [our Discord](https://discord.gg/MNQqqKg), planify regular calls, work on skills together, etc. It is very important for Leon to have a real community. At that moment, the skills platform will already be online, so it'll be easier to sync our progress and publish new skills.
|
||||
|
||||
- Feel free to check out the Git development branches and our [next major milestones](https://blog.getleon.ai/a-much-better-nlp-and-future-1-0-0-beta-7/#whats-next).
|
||||
- And the [detailed roadmap](http://roadmap.getleon.ai).
|
||||
- Many exciting things are coming up, hence no new documentation and test are going to be written until the official release of Leon.
|
||||
|
||||
---
|
||||
|
||||
## Latest Release
|
||||
|
||||
Check out the [latest release blog post](https://blog.getleon.ai/binaries-and-typescript-rewrite-1-0-0-beta-8/).
|
||||
|
||||
<a href="https://blog.getleon.ai/binaries-and-typescript-rewrite-1-0-0-beta-8/"><img width="400" src="https://blog.getleon.ai/static/a0d1cbafd1968e7531dc17e229f8cc61/aa440/beta-8.png" /></a>
|
||||
|
||||
---
|
||||
|
||||
@@ -68,8 +101,8 @@ If you want to, Leon can communicate with you by being **offline to protect your
|
||||
> - Skills
|
||||
> - The web app
|
||||
> - The hotword node
|
||||
> - The TCP server (for inter-process communication between Leon and third-party processes such as spaCy)
|
||||
> - The Python bridge (the connector between Python core and skills)
|
||||
> - The TCP server (for inter-process communication between Leon and third-party nodes such as spaCy)
|
||||
> - The Python bridge (the connector between the core and skills made with Python)
|
||||
|
||||
### What is Leon able to do?
|
||||
|
||||
@@ -81,7 +114,7 @@ Sounds good to you? Then let's get started!
|
||||
|
||||
## ☁️ Try with a Single-Click
|
||||
|
||||
Gitpod will automatically setup an environment and run an instance for you.
|
||||
Gitpod will automatically set up an environment and run an instance for you.
|
||||
|
||||
[](https://gitpod.io/#https://github.com/leon-ai/leon)
|
||||
|
||||
@@ -172,6 +205,13 @@ You'll find a write-up on this [blog post](https://blog.getleon.ai/the-story-beh
|
||||
<table>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td align="center" valign="middle" width="128">
|
||||
<a href="https://github.com/Appwrite">
|
||||
<img src="https://github.com/Appwrite.png?size=128" />
|
||||
Appwrite
|
||||
</a><br>
|
||||
<sub><sup>250 USD / month</sup></sub>
|
||||
</td>
|
||||
<td align="center" valign="middle" width="128">
|
||||
<img src="https://getleon.ai/img/anonymous.svg" width="128" />
|
||||
Anonymous
|
||||
@@ -179,21 +219,14 @@ You'll find a write-up on this [blog post](https://blog.getleon.ai/the-story-beh
|
||||
<sub><sup>100 USD / month</sup></sub>
|
||||
</td>
|
||||
<td align="center" valign="middle" width="128">
|
||||
<a href="https://github.com/phareal">
|
||||
<img src="https://github.com/phareal.png?size=128" />
|
||||
phareal
|
||||
</a><br>
|
||||
<sub><sup>30 USD / month</sup></sub>
|
||||
</td>
|
||||
<td align="center" valign="middle" width="128">
|
||||
<a href="https://github.com/KeithIMyers">
|
||||
<img src="https://github.com/KeithIMyers.png?size=128" />
|
||||
Keith Myers
|
||||
<a href="https://github.com/herbundkraut">
|
||||
<img src="https://github.com/herbundkraut.png?size=128" />
|
||||
herbundkraut
|
||||
</a><br>
|
||||
<sub><sup>10 USD / month</sup></sub>
|
||||
</td>
|
||||
<td align="center" valign="middle" width="128">
|
||||
<a href="http://sponsor.getleon.ai/">
|
||||
<a href="http://sponsor.getleon.ai/">
|
||||
You?
|
||||
</a>
|
||||
</td>
|
||||
@@ -220,7 +253,7 @@ The focus is not only limited to the activity you see on GitHub but also a lot o
|
||||
</a>
|
||||
|
||||
<a href="https://www.aoz.studio">
|
||||
<img src="https://user-images.githubusercontent.com/1731544/153794939-c42f1b10-a15d-4e82-b448-dc95bfe85b1c.png" alt="AOZ Studio" width="64" />
|
||||
<img src="https://getleon.ai/_next/image?url=%2Fimg%2Fthanks%2Faoz-studio.png&w=384&q=75" alt="AOZ Studio" width="128" />
|
||||
</a>
|
||||
|
||||
## 📝 License
|
||||
|
||||
+19
-5
@@ -125,8 +125,10 @@ table {
|
||||
}
|
||||
|
||||
:root {
|
||||
--black-color: #151718;
|
||||
--black-color: #000;
|
||||
--light-black-color: #222426;
|
||||
--white-color: #fff;
|
||||
--grey-color: #323739;
|
||||
}
|
||||
|
||||
a {
|
||||
@@ -193,7 +195,7 @@ input {
|
||||
color: var(--white-color);
|
||||
width: 100%;
|
||||
border: none;
|
||||
border-bottom: 2px solid var(--white-color);
|
||||
border-bottom: 2px solid var(--grey-color);
|
||||
background: none;
|
||||
font-weight: 400;
|
||||
font-size: 4em;
|
||||
@@ -222,7 +224,7 @@ small {
|
||||
top: 10%;
|
||||
height: 50%;
|
||||
overflow-y: auto;
|
||||
border: 2px solid var(--white-color);
|
||||
border: 2px solid var(--grey-color);
|
||||
border-radius: 12px;
|
||||
}
|
||||
#feed::-webkit-scrollbar {
|
||||
@@ -285,6 +287,17 @@ small {
|
||||
.bubble-container.leon {
|
||||
text-align: left;
|
||||
}
|
||||
.show-more {
|
||||
margin: 3px;
|
||||
text-decoration: underline;
|
||||
}
|
||||
.show-more:hover {
|
||||
cursor: pointer;
|
||||
text-decoration: none;
|
||||
}
|
||||
.show-all {
|
||||
max-height: 100% !important;
|
||||
}
|
||||
|
||||
.bubble {
|
||||
padding: 6px 12px;
|
||||
@@ -295,6 +308,7 @@ small {
|
||||
text-align: left;
|
||||
opacity: 0;
|
||||
animation: fadeIn 0.2s ease-in forwards;
|
||||
overflow: hidden;
|
||||
}
|
||||
#feed .me .bubble {
|
||||
background-color: #1c75db;
|
||||
@@ -302,8 +316,8 @@ small {
|
||||
right: 0;
|
||||
}
|
||||
#feed .leon .bubble {
|
||||
background-color: #eee;
|
||||
color: var(--black-color);
|
||||
background-color: var(--light-black-color);
|
||||
color: var(--white-color);
|
||||
}
|
||||
@keyframes fadeIn {
|
||||
100% {
|
||||
|
||||
@@ -9,6 +9,7 @@
|
||||
</head>
|
||||
<body class="settingup">
|
||||
<main>
|
||||
<div id="root"></div>
|
||||
<div id="feed">
|
||||
<p id="no-bubble" class="hide">
|
||||
You can start to interact with Leon, don't be shy.
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
function App() {
|
||||
return (
|
||||
<>
|
||||
<p>Hello from React</p>
|
||||
</>
|
||||
)
|
||||
}
|
||||
|
||||
export default App
|
||||
@@ -0,0 +1,9 @@
|
||||
import type React from 'react'
|
||||
|
||||
interface Props {
|
||||
children: React.ReactNode
|
||||
}
|
||||
|
||||
export function Button({ children }: Props) {
|
||||
return <button>{children}</button>
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
const MAXIMUM_HEIGHT_TO_SHOW_SEE_MORE = 340
|
||||
|
||||
export default class Chatbot {
|
||||
constructor() {
|
||||
this.et = new EventTarget()
|
||||
@@ -60,6 +62,9 @@ export default class Chatbot {
|
||||
}
|
||||
|
||||
loadFeed() {
|
||||
/**
|
||||
* TODO: widget: load widget from local storage
|
||||
*/
|
||||
return new Promise((resolve) => {
|
||||
if (this.parsedBubbles === null || this.parsedBubbles.length === 0) {
|
||||
this.noBubbleMessage.classList.remove('hide')
|
||||
@@ -92,6 +97,23 @@ export default class Chatbot {
|
||||
|
||||
this.feed.appendChild(container).appendChild(bubble)
|
||||
|
||||
if (container.clientHeight > MAXIMUM_HEIGHT_TO_SHOW_SEE_MORE) {
|
||||
bubble.style.maxHeight = `${MAXIMUM_HEIGHT_TO_SHOW_SEE_MORE}px`
|
||||
const showMore = document.createElement('p')
|
||||
const showMoreText = 'Show more'
|
||||
|
||||
showMore.className = 'show-more'
|
||||
showMore.innerHTML = showMoreText
|
||||
|
||||
container.appendChild(showMore)
|
||||
|
||||
showMore.addEventListener('click', () => {
|
||||
bubble.classList.toggle('show-all')
|
||||
showMore.innerHTML =
|
||||
showMore.innerHTML === showMoreText ? 'Show less' : showMoreText
|
||||
})
|
||||
}
|
||||
|
||||
if (save) {
|
||||
this.saveBubble(who, string)
|
||||
}
|
||||
|
||||
+51
-5
@@ -1,6 +1,10 @@
|
||||
import { io } from 'socket.io-client'
|
||||
import React from 'react'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
import { Button } from './aurora/button'
|
||||
import Chatbot from './chatbot'
|
||||
import { INIT_MESSAGES } from './constants'
|
||||
|
||||
export default class Client {
|
||||
constructor(client, serverUrl, input, res) {
|
||||
@@ -31,6 +35,24 @@ export default class Client {
|
||||
return this._recorder
|
||||
}
|
||||
|
||||
async sendInitMessages() {
|
||||
for (let i = 0; i < INIT_MESSAGES.length; i++) {
|
||||
const messages = INIT_MESSAGES[i]
|
||||
const message = messages[Math.floor(Math.random() * messages.length)]
|
||||
const sendingDelay = Math.floor(Math.random() * 2000) + 1000
|
||||
const typingFactorDelay = Math.floor(Math.random() * 4) + 2
|
||||
|
||||
setTimeout(() => {
|
||||
this.chatbot.isTyping('leon', true)
|
||||
}, sendingDelay / typingFactorDelay)
|
||||
|
||||
await new Promise((resolve) => setTimeout(resolve, sendingDelay))
|
||||
|
||||
this.chatbot.receivedFrom('leon', message)
|
||||
this.chatbot.isTyping('leon', false)
|
||||
}
|
||||
}
|
||||
|
||||
init(loader) {
|
||||
this.chatbot.init()
|
||||
|
||||
@@ -40,6 +62,10 @@ export default class Client {
|
||||
|
||||
this.socket.on('ready', () => {
|
||||
loader.stop()
|
||||
|
||||
if (this.chatbot.parsedBubbles?.length === 0) {
|
||||
this.sendInitMessages()
|
||||
}
|
||||
})
|
||||
|
||||
this.socket.on('answer', (data) => {
|
||||
@@ -63,6 +89,30 @@ export default class Client {
|
||||
cb('string-received')
|
||||
})
|
||||
|
||||
this.socket.on('widget', (data) => {
|
||||
/**
|
||||
* TODO: widget: widget handler to core/skill; dynamic component rendering
|
||||
*/
|
||||
console.log('data', data)
|
||||
|
||||
const container = document.createElement('div')
|
||||
container.className = 'widget'
|
||||
this.chatbot.feed.appendChild(container)
|
||||
|
||||
const root = createRoot(container)
|
||||
|
||||
// TODO: widget: pass props and dynamic component loading according to type
|
||||
const widgets = {
|
||||
Button: (options) => {
|
||||
return Button({
|
||||
children: options.text
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
root.render(widgets[data.type](data.options))
|
||||
})
|
||||
|
||||
this.socket.on('audio-forwarded', (data, cb) => {
|
||||
const ctx = new AudioContext()
|
||||
const source = ctx.createBufferSource()
|
||||
@@ -100,7 +150,7 @@ export default class Client {
|
||||
}
|
||||
}
|
||||
}
|
||||
}, 1000)
|
||||
}, 1_000)
|
||||
}, data.duration + 500)
|
||||
}
|
||||
})
|
||||
@@ -108,10 +158,6 @@ export default class Client {
|
||||
cb('audio-received')
|
||||
})
|
||||
|
||||
this.socket.on('download', (data) => {
|
||||
window.location = `${this.serverUrl}/api/v1/downloads?domain=${data.domain}&skill=${data.skill}`
|
||||
})
|
||||
|
||||
if (this.history !== null) {
|
||||
this.parsedHistory = JSON.parse(this.history)
|
||||
}
|
||||
|
||||
@@ -0,0 +1,21 @@
|
||||
export const INIT_MESSAGES = [
|
||||
[
|
||||
`Hello there! Glad to e-meet meet you, I'm Leon, your open-source personal assistant. While I'm still learning and improving, I promise to do my best to be helpful.`
|
||||
],
|
||||
[
|
||||
`<ul>
|
||||
<li>We've got lots in the works. Check out <a href="http://roadmap.getleon.ai/" target="_blank">our roadmap</a>.</li>
|
||||
<li>Stay updated on our progress by checking out our <a href="https://blog.getleon.ai/" target="_blank">blog</a>.</li>
|
||||
<li>You can also get updates straight to your inbox <a href="https://newsletter.getleon.ai/subscription/form" target="_blank">here</a>.</li>
|
||||
`
|
||||
],
|
||||
[
|
||||
`Come hang out with us <a href="https://discord.gg/MNQqqKg" target="_blank">on Discord</a>! Once we release our official version, our community will be working together to build new skills for me. You won't want to miss out on the fun!`
|
||||
],
|
||||
[
|
||||
`At the moment, I'm not using a large language model, but we're planning to incorporate some in the future to improve my abilities. You can <a href="https://github.com/leon-ai/leon#how-about-large-language-models-and-leon" target="_blank">learn more about our plans here</a>.`
|
||||
],
|
||||
[
|
||||
`Just so you know, my creator is working tirelessly to improve my skills and features, dedicating 75% of his free time to the project on top of his full-time job. If you'd like to help speed up my development, you can sponsor his work by clicking on this link: <strong><a href='http://sponsor.getleon.ai/' target='_blank'>sponsor.getleon.ai</a></strong>. Your support would mean a lot to us. Thank you for choosing me as your assistant!`
|
||||
]
|
||||
]
|
||||
+96
-96
@@ -1,4 +1,4 @@
|
||||
import request from 'superagent'
|
||||
import axios from 'axios'
|
||||
|
||||
import Loader from './loader'
|
||||
import Client from './client'
|
||||
@@ -11,126 +11,107 @@ const config = {
|
||||
server_host: import.meta.env.VITE_LEON_HOST,
|
||||
server_port: import.meta.env.VITE_LEON_PORT,
|
||||
min_decibels: -40, // Noise detection sensitivity
|
||||
max_blank_time: 1000 // Maximum time to consider a blank (ms)
|
||||
max_blank_time: 1_000 // Maximum time to consider a blank (ms)
|
||||
}
|
||||
const serverUrl =
|
||||
import.meta.env.VITE_LEON_NODE_ENV === 'production'
|
||||
? ''
|
||||
: `${config.server_host}:${config.server_port}`
|
||||
|
||||
document.addEventListener('DOMContentLoaded', () => {
|
||||
document.addEventListener('DOMContentLoaded', async () => {
|
||||
const loader = new Loader()
|
||||
|
||||
loader.start()
|
||||
|
||||
request.get(`${serverUrl}/api/v1/info`).end((err, res) => {
|
||||
if (err || !res.ok) {
|
||||
console.error(err.response.error.message)
|
||||
} else {
|
||||
const input = document.querySelector('#utterance')
|
||||
const mic = document.querySelector('#mic-button')
|
||||
const v = document.querySelector('#version small')
|
||||
const client = new Client(config.app, serverUrl, input, res.body)
|
||||
let rec = {}
|
||||
let chunks = []
|
||||
try {
|
||||
const response = await axios.get(`${serverUrl}/api/v1/info`)
|
||||
const input = document.querySelector('#utterance')
|
||||
const mic = document.querySelector('#mic-button')
|
||||
const v = document.querySelector('#version small')
|
||||
const client = new Client(config.app, serverUrl, input, response.data)
|
||||
let rec = {}
|
||||
let chunks = []
|
||||
|
||||
v.innerHTML += client.info.version
|
||||
v.innerHTML += client.info.version
|
||||
|
||||
client.init(loader)
|
||||
client.init(loader)
|
||||
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({ audio: true })
|
||||
.then((stream) => {
|
||||
if (MediaRecorder) {
|
||||
rec = new Recorder(stream, mic, client.info)
|
||||
client.recorder = rec
|
||||
if (navigator.mediaDevices && navigator.mediaDevices.getUserMedia) {
|
||||
navigator.mediaDevices
|
||||
.getUserMedia({ audio: true })
|
||||
.then((stream) => {
|
||||
if (MediaRecorder) {
|
||||
rec = new Recorder(stream, mic, client.info)
|
||||
client.recorder = rec
|
||||
|
||||
rec.ondataavailable((e) => {
|
||||
chunks.push(e.data)
|
||||
})
|
||||
rec.ondataavailable((e) => {
|
||||
chunks.push(e.data)
|
||||
})
|
||||
|
||||
rec.onstart(() => {
|
||||
/* */
|
||||
})
|
||||
rec.onstart(() => {
|
||||
/* */
|
||||
})
|
||||
|
||||
rec.onstop(() => {
|
||||
const blob = new Blob(chunks)
|
||||
chunks = []
|
||||
rec.enabled = false
|
||||
rec.onstop(() => {
|
||||
const blob = new Blob(chunks)
|
||||
chunks = []
|
||||
rec.enabled = false
|
||||
|
||||
// Ensure there are some data
|
||||
if (blob.size >= 1000) {
|
||||
client.socket.emit('recognize', blob)
|
||||
}
|
||||
})
|
||||
// Ensure there are some data
|
||||
if (blob.size >= 1_000) {
|
||||
client.socket.emit('recognize', blob)
|
||||
}
|
||||
})
|
||||
|
||||
listener.listening(
|
||||
stream,
|
||||
config.min_decibels,
|
||||
config.max_blank_time,
|
||||
() => {
|
||||
// Noise detected
|
||||
rec.noiseDetected = true
|
||||
},
|
||||
() => {
|
||||
// Noise ended
|
||||
listener.listening(
|
||||
stream,
|
||||
config.min_decibels,
|
||||
config.max_blank_time,
|
||||
() => {
|
||||
// Noise detected
|
||||
rec.noiseDetected = true
|
||||
},
|
||||
() => {
|
||||
// Noise ended
|
||||
|
||||
rec.noiseDetected = false
|
||||
if (rec.enabled && !rec.hotwordTriggered) {
|
||||
rec.stop()
|
||||
rec.enabled = false
|
||||
rec.hotwordTriggered = false
|
||||
rec.countSilenceAfterTalk = 0
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
client.socket.on('enable-record', () => {
|
||||
rec.hotwordTriggered = true
|
||||
rec.start()
|
||||
setTimeout(() => {
|
||||
rec.noiseDetected = false
|
||||
if (rec.enabled && !rec.hotwordTriggered) {
|
||||
rec.stop()
|
||||
rec.enabled = false
|
||||
rec.hotwordTriggered = false
|
||||
}, config.max_blank_time)
|
||||
rec.enabled = true
|
||||
})
|
||||
} else {
|
||||
console.error('MediaRecorder is not supported on your browser.')
|
||||
}
|
||||
})
|
||||
.catch((err) => {
|
||||
console.error(
|
||||
'MediaDevices.getUserMedia() threw the following error:',
|
||||
err
|
||||
rec.countSilenceAfterTalk = 0
|
||||
}
|
||||
}
|
||||
)
|
||||
})
|
||||
} else {
|
||||
console.error(
|
||||
'MediaDevices.getUserMedia() is not supported on your browser.'
|
||||
)
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
onkeydowndocument(e, () => {
|
||||
if (rec.enabled === false) {
|
||||
input.value = ''
|
||||
rec.start()
|
||||
rec.enabled = true
|
||||
client.socket.on('enable-record', () => {
|
||||
rec.hotwordTriggered = true
|
||||
rec.start()
|
||||
setTimeout(() => {
|
||||
rec.hotwordTriggered = false
|
||||
}, config.max_blank_time)
|
||||
rec.enabled = true
|
||||
})
|
||||
} else {
|
||||
rec.stop()
|
||||
rec.enabled = false
|
||||
console.error('MediaRecorder is not supported on your browser.')
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
input.addEventListener('keydown', (e) => {
|
||||
onkeydowninput(e, client)
|
||||
})
|
||||
|
||||
mic.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
.catch((err) => {
|
||||
console.error(
|
||||
'MediaDevices.getUserMedia() threw the following error:',
|
||||
err
|
||||
)
|
||||
})
|
||||
} else {
|
||||
console.error(
|
||||
'MediaDevices.getUserMedia() is not supported on your browser.'
|
||||
)
|
||||
}
|
||||
|
||||
document.addEventListener('keydown', (e) => {
|
||||
onkeydowndocument(e, () => {
|
||||
if (rec.enabled === false) {
|
||||
input.value = ''
|
||||
rec.start()
|
||||
rec.enabled = true
|
||||
} else {
|
||||
@@ -138,6 +119,25 @@ document.addEventListener('DOMContentLoaded', () => {
|
||||
rec.enabled = false
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
})
|
||||
|
||||
input.addEventListener('keydown', (e) => {
|
||||
onkeydowninput(e, client)
|
||||
})
|
||||
|
||||
mic.addEventListener('click', (e) => {
|
||||
e.preventDefault()
|
||||
|
||||
if (rec.enabled === false) {
|
||||
rec.start()
|
||||
rec.enabled = true
|
||||
} else {
|
||||
rec.stop()
|
||||
rec.enabled = false
|
||||
}
|
||||
})
|
||||
} catch (e) {
|
||||
alert(`Error: ${e.message}; ${JSON.stringify(e.response?.data)}`)
|
||||
console.error(e)
|
||||
}
|
||||
})
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"useDefineForClassFields": true,
|
||||
"skipLibCheck": true,
|
||||
"module": "ESNext",
|
||||
/* Bundler mode */
|
||||
"moduleResolution": "bundler",
|
||||
"allowImportingTsExtensions": true,
|
||||
"resolveJsonModule": true,
|
||||
"isolatedModules": true,
|
||||
"noEmit": true,
|
||||
"jsx": "react-jsx",
|
||||
|
||||
/* Linting */
|
||||
"strict": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"noFallthroughCasesInSwitch": true
|
||||
}
|
||||
}
|
||||
+13
-3
@@ -1,16 +1,26 @@
|
||||
import dns from 'node:dns'
|
||||
|
||||
import dotenv from 'dotenv'
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
dns.setDefaultResultOrder('verbatim')
|
||||
|
||||
// Map necessary Leon's env vars as Vite only expose VITE_*
|
||||
process.env.VITE_LEON_NODE_ENV = process.env.LEON_NODE_ENV
|
||||
process.env.VITE_LEON_HOST = process.env.LEON_HOST
|
||||
process.env.VITE_LEON_PORT = process.env.LEON_PORT
|
||||
|
||||
export default {
|
||||
export default defineConfig({
|
||||
root: 'app/src',
|
||||
build: {
|
||||
outDir: '../dist',
|
||||
emptyOutDir: true
|
||||
}
|
||||
}
|
||||
},
|
||||
server: {
|
||||
port: 3000
|
||||
},
|
||||
plugins: [react()]
|
||||
})
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
package-lock=false
|
||||
save-exact=true
|
||||
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"name": "leon-nodejs-bridge",
|
||||
"description": "Leon's Node.js bridge to communicate between the core and skills made with JavaScript",
|
||||
"main": "dist/bin/leon-nodejs-bridge.js",
|
||||
"author": {
|
||||
"name": "Louis Grenard",
|
||||
"email": "louis@getleon.ai",
|
||||
"url": "https://twitter.com/grenlouis"
|
||||
},
|
||||
"license": "MIT",
|
||||
"homepage": "https://getleon.ai",
|
||||
"bugs": {
|
||||
"url": "https://github.com/leon-ai/leon/issues"
|
||||
},
|
||||
"dependencies": {
|
||||
"axios": "1.4.0",
|
||||
"lodash": "4.17.21"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/lodash": "4.14.194"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
import { SKILLS_PATH as SKILLS_ROOT_PATH } from '@/constants'
|
||||
import type { SkillConfigSchema } from '@server/schemas/skill-schemas'
|
||||
|
||||
import type { IntentObject } from '@sdk/types'
|
||||
|
||||
const {
|
||||
argv: [, , INTENT_OBJ_FILE_PATH]
|
||||
} = process
|
||||
|
||||
export const INTENT_OBJECT: IntentObject = JSON.parse(
|
||||
fs.readFileSync(INTENT_OBJ_FILE_PATH as string, 'utf8')
|
||||
)
|
||||
export const SKILL_PATH = path.join(
|
||||
SKILLS_ROOT_PATH,
|
||||
INTENT_OBJECT.domain,
|
||||
INTENT_OBJECT.skill
|
||||
)
|
||||
export const SKILLS_PATH = SKILLS_ROOT_PATH
|
||||
export const SKILL_CONFIG: SkillConfigSchema = JSON.parse(
|
||||
fs.readFileSync(
|
||||
path.join(
|
||||
SKILL_PATH,
|
||||
'config',
|
||||
INTENT_OBJECT.extra_context_data.lang + '.json'
|
||||
),
|
||||
'utf8'
|
||||
)
|
||||
)
|
||||
export { LEON_VERSION, NODEJS_BRIDGE_VERSION } from '@/constants'
|
||||
@@ -0,0 +1,49 @@
|
||||
import path from 'node:path'
|
||||
|
||||
import type { ActionFunction, ActionParams } from '@sdk/types'
|
||||
import { INTENT_OBJECT } from '@bridge/constants'
|
||||
;(async (): Promise<void> => {
|
||||
const {
|
||||
domain,
|
||||
skill,
|
||||
action,
|
||||
lang,
|
||||
utterance,
|
||||
current_entities,
|
||||
entities,
|
||||
current_resolvers,
|
||||
resolvers,
|
||||
slots,
|
||||
extra_context_data
|
||||
} = INTENT_OBJECT
|
||||
|
||||
const params: ActionParams = {
|
||||
lang,
|
||||
utterance,
|
||||
current_entities,
|
||||
entities,
|
||||
current_resolvers,
|
||||
resolvers,
|
||||
slots,
|
||||
extra_context_data
|
||||
}
|
||||
|
||||
try {
|
||||
const actionModule = await import(
|
||||
path.join(
|
||||
process.cwd(),
|
||||
'skills',
|
||||
domain,
|
||||
skill,
|
||||
'src',
|
||||
'actions',
|
||||
`${action}.ts`
|
||||
)
|
||||
)
|
||||
const actionFunction: ActionFunction = actionModule.run
|
||||
|
||||
await actionFunction(params)
|
||||
} catch (e) {
|
||||
console.error(`Error while running "${skill}" skill "${action}" action:`, e)
|
||||
}
|
||||
})()
|
||||
@@ -0,0 +1,12 @@
|
||||
import { Widget } from '../widget'
|
||||
|
||||
// TODO: contains the button API. rendering engine <-> SDK
|
||||
interface ButtonOptions {
|
||||
text: string
|
||||
}
|
||||
|
||||
export class Button extends Widget<ButtonOptions> {
|
||||
public constructor(options: ButtonOptions) {
|
||||
super(options)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
import type {
|
||||
AnswerData,
|
||||
AnswerInput,
|
||||
AnswerOutput,
|
||||
AnswerConfig
|
||||
} from '@sdk/types'
|
||||
import { INTENT_OBJECT, SKILL_CONFIG } from '@bridge/constants'
|
||||
|
||||
class Leon {
|
||||
private static instance: Leon
|
||||
|
||||
constructor() {
|
||||
if (!Leon.instance) {
|
||||
Leon.instance = this
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Apply data to the answer
|
||||
* @param answerKey The answer key
|
||||
* @param data The data to apply
|
||||
* @example setAnswerData('key', { name: 'Leon' })
|
||||
*/
|
||||
public setAnswerData(
|
||||
answerKey: string,
|
||||
data: AnswerData = null
|
||||
): AnswerConfig {
|
||||
try {
|
||||
// In case the answer key is a raw answer
|
||||
if (SKILL_CONFIG.answers == null || !SKILL_CONFIG.answers[answerKey]) {
|
||||
return answerKey
|
||||
}
|
||||
|
||||
const answers = SKILL_CONFIG.answers[answerKey] ?? ''
|
||||
let answer: AnswerConfig
|
||||
|
||||
if (Array.isArray(answers)) {
|
||||
answer = answers[Math.floor(Math.random() * answers.length)] ?? ''
|
||||
} else {
|
||||
answer = answers
|
||||
}
|
||||
|
||||
if (data != null) {
|
||||
for (const key in data) {
|
||||
// In case the answer needs speech and text differentiation
|
||||
if (typeof answer !== 'string' && answer.text) {
|
||||
answer.text = answer.text.replaceAll(`%${key}%`, String(data[key]))
|
||||
answer.speech = answer.speech.replaceAll(
|
||||
`%${key}%`,
|
||||
String(data[key])
|
||||
)
|
||||
} else {
|
||||
answer = (answer as string).replaceAll(
|
||||
`%${key}%`,
|
||||
String(data[key])
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (SKILL_CONFIG.variables) {
|
||||
const { variables } = SKILL_CONFIG
|
||||
|
||||
for (const key in variables) {
|
||||
// In case the answer needs speech and text differentiation
|
||||
if (typeof answer !== 'string' && answer.text) {
|
||||
answer.text = answer.text.replaceAll(
|
||||
`%${key}%`,
|
||||
String(variables[key])
|
||||
)
|
||||
answer.speech = answer.speech.replaceAll(
|
||||
`%${key}%`,
|
||||
String(variables[key])
|
||||
)
|
||||
} else {
|
||||
answer = (answer as string).replaceAll(
|
||||
`%${key}%`,
|
||||
String(variables[key])
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return answer
|
||||
} catch (e) {
|
||||
console.error('Error while setting answer data:', e)
|
||||
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Send an answer to the core
|
||||
* @param answerInput The answer input
|
||||
* @example answer({ key: 'greet' }) // 'Hello world'
|
||||
* @example answer({ key: 'welcome', data: { name: 'Louis' } }) // 'Welcome Louis'
|
||||
* @example answer({ key: 'confirm', core: { restart: true } }) // 'Would you like to retry?'
|
||||
*/
|
||||
public async answer(answerInput: AnswerInput): Promise<void> {
|
||||
try {
|
||||
const answerObject: AnswerOutput = {
|
||||
...INTENT_OBJECT,
|
||||
output: {
|
||||
codes:
|
||||
answerInput.widget && !answerInput.key
|
||||
? 'widget'
|
||||
: (answerInput.key as string),
|
||||
answer:
|
||||
answerInput.key != null
|
||||
? this.setAnswerData(answerInput.key, answerInput.data)
|
||||
: '',
|
||||
core: answerInput.core
|
||||
}
|
||||
}
|
||||
|
||||
if (answerInput.widget) {
|
||||
answerObject.output.widget = answerInput.widget
|
||||
}
|
||||
|
||||
// "Temporize" for the data buffer output on the core
|
||||
await new Promise((r) => setTimeout(r, 100))
|
||||
|
||||
process.stdout.write(JSON.stringify(answerObject))
|
||||
} catch (e) {
|
||||
console.error('Error while creating answer:', e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export const leon = new Leon()
|
||||
@@ -0,0 +1,99 @@
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
|
||||
import { SKILL_PATH, SKILLS_PATH } from '@bridge/constants'
|
||||
|
||||
interface MemoryOptions<T> {
|
||||
name: string
|
||||
defaultMemory?: T
|
||||
}
|
||||
|
||||
export class Memory<T = unknown> {
|
||||
private readonly memoryPath: string
|
||||
private readonly name: string
|
||||
private readonly defaultMemory: T | undefined
|
||||
private isFromAnotherSkill: boolean
|
||||
|
||||
constructor(options: MemoryOptions<T>) {
|
||||
const { name, defaultMemory } = options
|
||||
|
||||
this.name = name
|
||||
this.defaultMemory = defaultMemory
|
||||
this.memoryPath = path.join(SKILL_PATH, 'memory', `${this.name}.json`)
|
||||
this.isFromAnotherSkill = false
|
||||
|
||||
if (this.name.includes(':') && this.name.split(':').length === 3) {
|
||||
this.isFromAnotherSkill = true
|
||||
const [domainName, skillName, memoryName] = this.name.split(':')
|
||||
this.memoryPath = path.join(
|
||||
SKILLS_PATH,
|
||||
domainName as string,
|
||||
skillName as string,
|
||||
'memory',
|
||||
`${memoryName}.json`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the memory and set it to the default memory value
|
||||
* @example clear()
|
||||
*/
|
||||
public async clear(): Promise<void> {
|
||||
if (!this.isFromAnotherSkill) {
|
||||
await this.write(this.defaultMemory as T)
|
||||
} else {
|
||||
throw new Error(
|
||||
`You cannot clear the memory "${this.name}" as it belongs to another skill`
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Read the memory
|
||||
* @example read()
|
||||
*/
|
||||
public async read(): Promise<T> {
|
||||
if (this.isFromAnotherSkill && !fs.existsSync(this.memoryPath)) {
|
||||
throw new Error(
|
||||
`You cannot read the memory "${this.name}" as it belongs to another skill which haven't written to this memory yet`
|
||||
)
|
||||
}
|
||||
|
||||
try {
|
||||
if (!fs.existsSync(this.memoryPath)) {
|
||||
await this.clear()
|
||||
}
|
||||
|
||||
return JSON.parse(await fs.promises.readFile(this.memoryPath, 'utf-8'))
|
||||
} catch (e) {
|
||||
console.error(`Error while reading memory for "${this.name}":`, e)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Write the memory
|
||||
* @param memory The memory to write
|
||||
* @example write({ foo: 'bar' }) // { foo: 'bar' }
|
||||
*/
|
||||
public async write(memory: T): Promise<T> {
|
||||
if (!this.isFromAnotherSkill) {
|
||||
try {
|
||||
await fs.promises.writeFile(
|
||||
this.memoryPath,
|
||||
JSON.stringify(memory, null, 2)
|
||||
)
|
||||
|
||||
return memory
|
||||
} catch (e) {
|
||||
console.error(`Error while writing memory for "${this.name}":`, e)
|
||||
throw e
|
||||
}
|
||||
} else {
|
||||
throw new Error(
|
||||
`You cannot write into the memory "${this.name}" as it belongs to another skill`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,147 @@
|
||||
import dns from 'node:dns'
|
||||
|
||||
import type { AxiosInstance } from 'axios'
|
||||
import axios from 'axios'
|
||||
|
||||
import { LEON_VERSION, NODEJS_BRIDGE_VERSION } from '@bridge/constants'
|
||||
|
||||
interface NetworkOptions {
|
||||
/** `baseURL` will be prepended to `url`. It can be convenient to set `baseURL` for an instance of `Network` to pass relative URLs. */
|
||||
baseURL?: string
|
||||
}
|
||||
|
||||
interface NetworkRequestOptions {
|
||||
/** Server URL that will be used for the request. */
|
||||
url: string
|
||||
|
||||
/** Request method to be used when making the request. */
|
||||
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
|
||||
|
||||
/** Data to be sent as the request body. */
|
||||
data?: Record<string, unknown>
|
||||
|
||||
/** Custom headers to be sent. */
|
||||
headers?: Record<string, string>
|
||||
}
|
||||
|
||||
interface NetworkResponse<ResponseData> {
|
||||
/** Data provided by the server. */
|
||||
data: ResponseData
|
||||
|
||||
/** HTTP status code from the server response. */
|
||||
statusCode: number
|
||||
|
||||
/** Options that was provided for the request. */
|
||||
options: NetworkRequestOptions & NetworkOptions
|
||||
}
|
||||
|
||||
export class NetworkError<ResponseErrorData = unknown> extends Error {
|
||||
public readonly response: NetworkResponse<ResponseErrorData>
|
||||
|
||||
public constructor(response: NetworkResponse<ResponseErrorData>) {
|
||||
super(`[NetworkError]: ${response.statusCode} ${response.data}`)
|
||||
this.response = response
|
||||
Object.setPrototypeOf(this, NetworkError.prototype)
|
||||
}
|
||||
}
|
||||
|
||||
export class Network {
|
||||
private readonly options: NetworkOptions
|
||||
private axios: AxiosInstance
|
||||
|
||||
public constructor(options: NetworkOptions = {}) {
|
||||
this.options = options
|
||||
this.axios = axios.create({
|
||||
baseURL: this.options.baseURL
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Send HTTP request
|
||||
* @param options Request options
|
||||
* @example request({ url: '/send', method: 'POST', data: { message: 'Hi' } })
|
||||
*/
|
||||
public async request<ResponseData = unknown, ResponseErrorData = unknown>(
|
||||
options: NetworkRequestOptions
|
||||
): Promise<NetworkResponse<ResponseData>> {
|
||||
try {
|
||||
const response = await this.axios.request<string>({
|
||||
url: options.url,
|
||||
method: options.method.toLowerCase(),
|
||||
data: options.data,
|
||||
transformResponse: (data) => {
|
||||
return data
|
||||
},
|
||||
headers: {
|
||||
'User-Agent': `Leon Personal Assistant ${LEON_VERSION} - Node.js Bridge ${NODEJS_BRIDGE_VERSION}`,
|
||||
...options.headers
|
||||
}
|
||||
})
|
||||
|
||||
let data = {} as ResponseData
|
||||
try {
|
||||
data = JSON.parse(response.data)
|
||||
} catch {
|
||||
data = response.data as ResponseData
|
||||
}
|
||||
|
||||
return {
|
||||
data,
|
||||
statusCode: response.status,
|
||||
options: {
|
||||
...this.options,
|
||||
...options
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
let statusCode = 500
|
||||
let dataRawText = ''
|
||||
|
||||
if (axios.isAxiosError(error)) {
|
||||
dataRawText = error?.response?.data ?? ''
|
||||
statusCode = error?.response?.status ?? 500
|
||||
}
|
||||
|
||||
let data: ResponseErrorData
|
||||
try {
|
||||
data = JSON.parse(dataRawText)
|
||||
} catch {
|
||||
data = dataRawText as ResponseErrorData
|
||||
}
|
||||
|
||||
throw new NetworkError<ResponseErrorData>({
|
||||
data,
|
||||
statusCode,
|
||||
options: {
|
||||
...this.options,
|
||||
...options
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if error is a network error
|
||||
* @param error Error to check
|
||||
* @example isNetworkError(error) // false
|
||||
*/
|
||||
public isNetworkError<ResponseErrorData = unknown>(
|
||||
error: unknown
|
||||
): error is NetworkError<ResponseErrorData> {
|
||||
return error instanceof NetworkError
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify whether there is an Internet connectivity
|
||||
* @example isNetworkAvailable() // true
|
||||
*/
|
||||
public async isNetworkAvailable(): Promise<boolean> {
|
||||
try {
|
||||
await dns.promises.resolve('getleon.ai')
|
||||
|
||||
return true
|
||||
} catch {
|
||||
return false
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export { default } from 'lodash'
|
||||
@@ -0,0 +1,125 @@
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
|
||||
import { SKILL_PATH } from '@bridge/constants'
|
||||
|
||||
export class Settings<T extends Record<string, unknown>> {
|
||||
private readonly settingsPath: string
|
||||
private readonly settingsSamplePath: string
|
||||
|
||||
public constructor() {
|
||||
this.settingsPath = path.join(SKILL_PATH, 'src', 'settings.json')
|
||||
this.settingsSamplePath = path.join(
|
||||
SKILL_PATH,
|
||||
'src',
|
||||
'settings.sample.json'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a setting is already set
|
||||
* @param key The key to verify whether its value is set
|
||||
* @returns isSettingSet('apiKey') // true
|
||||
*/
|
||||
public async isSettingSet(key: string): Promise<boolean> {
|
||||
const settingsSample = await this.getSettingsSample()
|
||||
const settings = await this.get()
|
||||
|
||||
return (
|
||||
!!settings[key] &&
|
||||
JSON.stringify(settings[key]) !== JSON.stringify(settingsSample[key])
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Clear the settings and set it to the default settings.sample.json file
|
||||
* @example clear()
|
||||
*/
|
||||
public async clear(): Promise<void> {
|
||||
const settingsSample = await this.getSettingsSample()
|
||||
|
||||
await this.set(settingsSample)
|
||||
}
|
||||
|
||||
private async getSettingsSample(): Promise<T> {
|
||||
try {
|
||||
return JSON.parse(
|
||||
await fs.promises.readFile(this.settingsSamplePath, 'utf8')
|
||||
)
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`Error while reading settings sample at "${this.settingsSamplePath}":`,
|
||||
e
|
||||
)
|
||||
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the settings
|
||||
* @param key The key of the setting to get
|
||||
* @example get('API_KEY') // 'value'
|
||||
* @example get() // { API_KEY: 'value' }
|
||||
*/
|
||||
public async get<Key extends keyof T>(key: Key): Promise<T[Key]>
|
||||
public async get(): Promise<T>
|
||||
public async get<Key extends keyof T>(key?: Key): Promise<T | T[Key]> {
|
||||
try {
|
||||
if (!fs.existsSync(this.settingsPath)) {
|
||||
await this.clear()
|
||||
}
|
||||
|
||||
const settings = JSON.parse(
|
||||
await fs.promises.readFile(this.settingsPath, 'utf8')
|
||||
)
|
||||
|
||||
if (key != null) {
|
||||
return settings[key]
|
||||
}
|
||||
|
||||
return settings
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`Error while reading settings at "${this.settingsPath}":`,
|
||||
e
|
||||
)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the settings
|
||||
* @param key The key of the setting to set
|
||||
* @param value The value of the setting to set
|
||||
* @example set({ API_KEY: 'value' }) // { API_KEY: 'value' }
|
||||
*/
|
||||
public async set<Key extends keyof T>(key: Key, value: T[Key]): Promise<T>
|
||||
public async set(settings: T): Promise<T>
|
||||
public async set<Key extends keyof T>(
|
||||
keyOrSettings: Key | T,
|
||||
value?: T[Key]
|
||||
): Promise<T> {
|
||||
try {
|
||||
const settings = await this.get()
|
||||
const newSettings =
|
||||
typeof keyOrSettings === 'object'
|
||||
? keyOrSettings
|
||||
: { ...settings, [keyOrSettings]: value }
|
||||
|
||||
await fs.promises.writeFile(
|
||||
this.settingsPath,
|
||||
JSON.stringify(newSettings, null, 2)
|
||||
)
|
||||
|
||||
return newSettings
|
||||
} catch (e) {
|
||||
console.error(
|
||||
`Error while writing settings at "${this.settingsPath}":`,
|
||||
e
|
||||
)
|
||||
|
||||
throw e
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Action types
|
||||
*/
|
||||
import type {
|
||||
ActionParams,
|
||||
IntentObject,
|
||||
SkillAnswerCoreData,
|
||||
SkillAnswerOutput
|
||||
} from '@/core/brain/types'
|
||||
import type { SkillAnswerConfigSchema } from '@/schemas/skill-schemas'
|
||||
|
||||
export type { ActionParams, IntentObject }
|
||||
|
||||
export * from '@/core/nlp/types'
|
||||
|
||||
export type ActionFunction = (params: ActionParams) => Promise<void>
|
||||
|
||||
/**
|
||||
* Answer types
|
||||
*/
|
||||
export interface Answer {
|
||||
key?: string
|
||||
widget?: unknown // TODO
|
||||
data?: AnswerData
|
||||
core?: SkillAnswerCoreData
|
||||
}
|
||||
export interface TextAnswer extends Answer {
|
||||
key: string
|
||||
}
|
||||
export interface WidgetAnswer extends Answer {
|
||||
widget: unknown
|
||||
key?: string
|
||||
}
|
||||
export type AnswerData = Record<string, string | number> | null
|
||||
export type AnswerInput = TextAnswer | WidgetAnswer
|
||||
export type AnswerOutput = SkillAnswerOutput
|
||||
export type AnswerConfig = SkillAnswerConfigSchema
|
||||
@@ -0,0 +1,9 @@
|
||||
export abstract class Widget<T> {
|
||||
public readonly type: string
|
||||
public readonly options: T
|
||||
|
||||
protected constructor(options: T) {
|
||||
this.type = this.constructor.name
|
||||
this.options = options
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const VERSION = '1.1.0'
|
||||
@@ -0,0 +1,24 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/bin",
|
||||
"rootDir": "../../",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@@/*": ["../../*"],
|
||||
"@/*": ["../../server/src/*"],
|
||||
"@server/*": ["../../server/src/*"],
|
||||
"@bridge/*": ["./src/*"],
|
||||
"@sdk/*": ["./src/sdk/*"]
|
||||
},
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"declaration": true
|
||||
},
|
||||
"ts-node": {
|
||||
"swc": true,
|
||||
"require": ["tsconfig-paths/register"],
|
||||
"files": true
|
||||
},
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["node_modules", "dist"]
|
||||
}
|
||||
@@ -11,6 +11,4 @@ setuptools = "*"
|
||||
wheel = "*"
|
||||
cx-freeze = "==6.11.1"
|
||||
requests = "==2.28.1"
|
||||
pytube = "==9.5.0"
|
||||
tinydb = "==4.7.0"
|
||||
beautifulsoup4 = "==4.7.1"
|
||||
|
||||
@@ -0,0 +1,30 @@
|
||||
import sys
|
||||
import json
|
||||
import os
|
||||
|
||||
import version
|
||||
|
||||
INTENT_OBJ_FILE_PATH = sys.argv[1]
|
||||
|
||||
with open(INTENT_OBJ_FILE_PATH, 'r') as f:
|
||||
INTENT_OBJECT = json.load(f)
|
||||
|
||||
SKILLS_ROOT_PATH = os.path.join(
|
||||
os.getcwd(),
|
||||
'skills'
|
||||
)
|
||||
|
||||
SKILL_PATH = os.path.join(
|
||||
SKILLS_ROOT_PATH,
|
||||
INTENT_OBJECT['domain'],
|
||||
INTENT_OBJECT['skill']
|
||||
)
|
||||
|
||||
SKILLS_PATH = SKILLS_ROOT_PATH
|
||||
|
||||
with open(os.path.join(SKILL_PATH, 'config', INTENT_OBJECT['extra_context_data']['lang'] + '.json'), 'r') as f:
|
||||
SKILL_CONFIG = json.load(f)
|
||||
|
||||
LEON_VERSION = os.getenv('npm_package_version')
|
||||
|
||||
PYTHON_BRIDGE_VERSION = version.__version__
|
||||
+34
-27
@@ -1,37 +1,44 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import utils
|
||||
import sys
|
||||
from traceback import print_exc
|
||||
from sys import path
|
||||
from json import dumps, loads
|
||||
from importlib import import_module
|
||||
|
||||
from constants import INTENT_OBJECT
|
||||
|
||||
|
||||
def main():
|
||||
"""Dynamically import skills related to the args and print the output"""
|
||||
params = {
|
||||
'lang': INTENT_OBJECT['lang'],
|
||||
'utterance': INTENT_OBJECT['utterance'],
|
||||
'current_entities': INTENT_OBJECT['current_entities'],
|
||||
'entities': INTENT_OBJECT['entities'],
|
||||
'current_resolvers': INTENT_OBJECT['current_resolvers'],
|
||||
'resolvers': INTENT_OBJECT['resolvers'],
|
||||
'slots': INTENT_OBJECT['slots'],
|
||||
'extra_context_data': INTENT_OBJECT['extra_context_data']
|
||||
}
|
||||
|
||||
path.append('.')
|
||||
try:
|
||||
sys.path.append('.')
|
||||
|
||||
intent_obj = utils.get_intent_obj()
|
||||
skill_action_module = import_module(
|
||||
'skills.'
|
||||
+ INTENT_OBJECT['domain']
|
||||
+ '.'
|
||||
+ INTENT_OBJECT['skill']
|
||||
+ '.src.actions.'
|
||||
+ INTENT_OBJECT['action']
|
||||
)
|
||||
|
||||
skill = import_module('skills.' + intent_obj['domain'] + '.' + intent_obj['skill'] + '.src.actions.' + intent_obj['action'])
|
||||
getattr(skill_action_module, 'run')(params)
|
||||
except Exception as e:
|
||||
print(f"Error while running {INTENT_OBJECT['skill']} skill {INTENT_OBJECT['action']} action: {e}")
|
||||
print_exc()
|
||||
|
||||
params = {
|
||||
'lang': intent_obj['lang'],
|
||||
'utterance': intent_obj['utterance'],
|
||||
'current_entities': intent_obj['current_entities'],
|
||||
'entities': intent_obj['entities'],
|
||||
'current_resolvers': intent_obj['current_resolvers'],
|
||||
'resolvers': intent_obj['resolvers'],
|
||||
'slots': intent_obj['slots']
|
||||
}
|
||||
|
||||
return getattr(skill, intent_obj['action'])(params)
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
raise main()
|
||||
except Exception as e:
|
||||
# Print full traceback error report if skills triggers an error from the call stack
|
||||
if 'exceptions must derive from BaseException' not in str(e):
|
||||
print_exc()
|
||||
try:
|
||||
raise main()
|
||||
except Exception as e:
|
||||
# Print full traceback error report if skills triggers an error from the call stack
|
||||
if 'exceptions must derive from BaseException' not in str(e):
|
||||
print_exc()
|
||||
|
||||
@@ -0,0 +1,9 @@
|
||||
from ..widget import Widget
|
||||
|
||||
|
||||
# TODO: contains the button API. rendering engine <-> SDK
|
||||
|
||||
|
||||
class Button(Widget):
|
||||
def __init__(self, options):
|
||||
super().__init__(options)
|
||||
@@ -0,0 +1,92 @@
|
||||
import random
|
||||
import sys
|
||||
from typing import Union
|
||||
from time import sleep
|
||||
import json
|
||||
|
||||
from .types import AnswerInput, AnswerData, AnswerConfig
|
||||
from ..constants import SKILL_CONFIG, INTENT_OBJECT
|
||||
|
||||
|
||||
class Leon:
|
||||
instance: 'Leon' = None
|
||||
|
||||
def __init__(self) -> None:
|
||||
if not Leon.instance:
|
||||
Leon.instance = self
|
||||
|
||||
@staticmethod
|
||||
def set_answer_data(answer_key: str, data: Union[AnswerData, None] = None) -> Union[str, AnswerConfig]:
|
||||
"""
|
||||
Apply data to the answer
|
||||
:param answer_key: The answer key
|
||||
:param data: The data to apply
|
||||
"""
|
||||
try:
|
||||
# In case the answer key is a raw answer
|
||||
if SKILL_CONFIG.get('answers') is None or SKILL_CONFIG['answers'].get(answer_key) is None:
|
||||
return answer_key
|
||||
|
||||
answers = SKILL_CONFIG['answers'].get(answer_key, '')
|
||||
if isinstance(answers, list):
|
||||
answer = answers[random.randrange(len(answers))]
|
||||
else:
|
||||
answer = answers
|
||||
|
||||
if data:
|
||||
for key, value in data.items():
|
||||
# In case the answer needs speech and text differentiation
|
||||
if not isinstance(answer, str) and answer.get('text'):
|
||||
answer['text'] = answer['text'].replace('%{}%'.format(key), str(value))
|
||||
answer['speech'] = answer['speech'].replace('%{}%'.format(key), str(value))
|
||||
else:
|
||||
answer = answer.replace('%{}%'.format(key), str(value))
|
||||
|
||||
if SKILL_CONFIG.get('variables'):
|
||||
for key, value in SKILL_CONFIG['variables'].items():
|
||||
# In case the answer needs speech and text differentiation
|
||||
if not isinstance(answer, str) and answer.get('text'):
|
||||
answer['text'] = answer['text'].replace('%{}%'.format(key), str(value))
|
||||
answer['speech'] = answer['speech'].replace('%{}%'.format(key), str(value))
|
||||
else:
|
||||
answer = answer.replace('%{}%'.format(key), str(value))
|
||||
|
||||
return answer
|
||||
except Exception as e:
|
||||
print('Error while setting answer data:', e)
|
||||
raise e
|
||||
|
||||
def answer(self, answer_input: AnswerInput) -> None:
|
||||
"""
|
||||
Send an answer to the core
|
||||
:param answer_input: The answer input
|
||||
"""
|
||||
try:
|
||||
key = answer_input.get('key')
|
||||
output = {
|
||||
'output': {
|
||||
'codes': 'widget' if answer_input.get('widget') and not answer_input.get('key') else answer_input.get('key'),
|
||||
'answer': self.set_answer_data(key, answer_input.get('data')) if key is not None else '',
|
||||
'core': answer_input.get('core')
|
||||
}
|
||||
}
|
||||
|
||||
if answer_input.get('widget'):
|
||||
output['output']['widget'] = answer_input['widget'].__dict__
|
||||
|
||||
answer_object = {
|
||||
**INTENT_OBJECT,
|
||||
**output
|
||||
}
|
||||
|
||||
# "Temporize" for the data buffer output on the core
|
||||
sleep(0.1)
|
||||
|
||||
sys.stdout.write(json.dumps(answer_object))
|
||||
sys.stdout.flush()
|
||||
|
||||
except Exception as e:
|
||||
print('Error while creating answer:', e)
|
||||
|
||||
|
||||
leon = Leon()
|
||||
@@ -0,0 +1,76 @@
|
||||
import json
|
||||
import os
|
||||
from typing import TypedDict, Any
|
||||
|
||||
from ..constants import SKILL_PATH, SKILLS_PATH
|
||||
|
||||
|
||||
class MemoryOptions(TypedDict, total=False):
|
||||
name: str
|
||||
default_memory: Any
|
||||
|
||||
|
||||
class Memory:
|
||||
def __init__(self, options: MemoryOptions):
|
||||
self.name = options['name']
|
||||
self.default_memory = options['default_memory'] if 'default_memory' in options else None
|
||||
self.memory_path = self.memory_path = os.path.join(
|
||||
SKILL_PATH,
|
||||
'memory',
|
||||
f'{self.name}.json'
|
||||
)
|
||||
self.__is_from_another_skill = False
|
||||
|
||||
if ':' in self.name and self.name.count(':') == 2:
|
||||
self.__is_from_another_skill = True
|
||||
domain_name, skill_name, memory_name = self.name.split(':')
|
||||
self.memory_path = os.path.join(
|
||||
SKILLS_PATH,
|
||||
domain_name,
|
||||
skill_name,
|
||||
'memory',
|
||||
memory_name + '.json'
|
||||
)
|
||||
|
||||
def clear(self) -> None:
|
||||
"""
|
||||
Clear the memory and set it to the default memory value
|
||||
"""
|
||||
if not self.__is_from_another_skill:
|
||||
self.write(self.default_memory)
|
||||
else:
|
||||
raise ValueError(f'You cannot clear the memory "{self.name}" as it belongs to another skill')
|
||||
|
||||
def read(self):
|
||||
"""
|
||||
Read the memory
|
||||
"""
|
||||
if self.__is_from_another_skill and not os.path.exists(self.memory_path):
|
||||
raise ValueError(f'You cannot read the memory "{self.name}" as it belongs to another skill which hasn\'t written to this memory yet')
|
||||
|
||||
try:
|
||||
if not os.path.exists(self.memory_path):
|
||||
self.clear()
|
||||
|
||||
with open(self.memory_path, 'r') as f:
|
||||
return json.load(f)
|
||||
except Exception as e:
|
||||
print(f'Error while reading memory for "{self.name}": {e}')
|
||||
raise e
|
||||
|
||||
def write(self, memory):
|
||||
"""
|
||||
Write the memory
|
||||
:param memory: The memory to write
|
||||
"""
|
||||
if not self.__is_from_another_skill:
|
||||
try:
|
||||
with open(self.memory_path, 'w') as f:
|
||||
json.dump(memory, f, indent=2)
|
||||
|
||||
return memory
|
||||
except Exception as e:
|
||||
print(f'Error while writing memory for "{self.name}": {e}')
|
||||
raise e
|
||||
else:
|
||||
raise ValueError(f'You cannot write into the memory "{self.name}" as it belongs to another skill')
|
||||
@@ -0,0 +1,92 @@
|
||||
import requests
|
||||
import socket
|
||||
from typing import Any, Dict, TypedDict, Union, Literal, Optional
|
||||
|
||||
from ..constants import LEON_VERSION, PYTHON_BRIDGE_VERSION
|
||||
|
||||
|
||||
class NetworkOptions(TypedDict, total=False):
|
||||
base_url: Optional[str]
|
||||
|
||||
|
||||
class NetworkResponse(TypedDict):
|
||||
data: Any
|
||||
status_code: int
|
||||
options: Dict[str, Any]
|
||||
|
||||
|
||||
class NetworkError(Exception):
|
||||
def __init__(self, response: NetworkResponse) -> None:
|
||||
self.response = response
|
||||
super().__init__(f"[NetworkError]: {response['status_code']} {response['data']}")
|
||||
|
||||
|
||||
class NetworkRequestOptions(TypedDict, total=False):
|
||||
url: str
|
||||
method: Union[Literal['GET'], Literal['POST'], Literal['PUT'], Literal['PATCH'], Literal['DELETE']]
|
||||
data: Dict[str, Any]
|
||||
headers: Dict[str, str]
|
||||
|
||||
|
||||
class Network:
|
||||
def __init__(self, options: NetworkOptions = {'base_url': None}) -> None:
|
||||
self.options = options
|
||||
|
||||
def request(self, options: NetworkRequestOptions) -> NetworkResponse:
|
||||
try:
|
||||
url = options['url']
|
||||
|
||||
if self.options['base_url'] is not None:
|
||||
url = self.options['base_url'] + url
|
||||
method = options['method']
|
||||
data = options.get('data', {})
|
||||
headers = options.get('headers', {})
|
||||
response = requests.request(
|
||||
method,
|
||||
url,
|
||||
json=data,
|
||||
headers={
|
||||
'User-Agent': f"Leon Personal Assistant {LEON_VERSION} - Python Bridge {PYTHON_BRIDGE_VERSION}",
|
||||
**headers
|
||||
}
|
||||
)
|
||||
|
||||
data = {}
|
||||
try:
|
||||
data = response.json()
|
||||
except Exception:
|
||||
data = response.text
|
||||
|
||||
network_response: NetworkResponse = {
|
||||
'data': data,
|
||||
'status_code': response.status_code,
|
||||
'options': {**self.options, **options}
|
||||
}
|
||||
|
||||
if response.ok:
|
||||
return network_response
|
||||
else:
|
||||
raise NetworkError(network_response)
|
||||
except requests.exceptions.RequestException as error:
|
||||
data = {}
|
||||
try:
|
||||
data = error.response.json()
|
||||
except Exception:
|
||||
data = error.response.text
|
||||
|
||||
raise NetworkError({
|
||||
'data': data,
|
||||
'status_code': error.response.status_code,
|
||||
'options': {**self.options, **options}
|
||||
}) from error
|
||||
|
||||
def is_network_error(self, error: Exception) -> bool:
|
||||
return isinstance(error, NetworkError)
|
||||
|
||||
def is_network_available(self) -> bool:
|
||||
try:
|
||||
socket.gethostbyname('getleon.ai')
|
||||
|
||||
return True
|
||||
except socket.error:
|
||||
return False
|
||||
@@ -0,0 +1,91 @@
|
||||
import json
|
||||
import os
|
||||
from os import path
|
||||
from typing import Union, Any, overload
|
||||
|
||||
from ..constants import SKILL_PATH
|
||||
|
||||
|
||||
class Settings:
|
||||
def __init__(self):
|
||||
self.settings_path = path.join(SKILL_PATH, 'src', 'settings.json')
|
||||
self.settings_sample_path = path.join(SKILL_PATH, 'src', 'settings.sample.json')
|
||||
|
||||
def is_setting_set(self, key: str) -> bool:
|
||||
"""
|
||||
Check if a setting is already set
|
||||
:param key: The key to verify whether its value is set
|
||||
"""
|
||||
settings_sample = self.get_settings_sample()
|
||||
settings = self.get()
|
||||
|
||||
return key in settings and json.dumps(settings[key]) != json.dumps(settings_sample[key])
|
||||
|
||||
def clear(self) -> None:
|
||||
"""
|
||||
Clear the settings and set it to the default settings.sample.json file
|
||||
"""
|
||||
settings_sample = self.get_settings_sample()
|
||||
self.set(settings_sample)
|
||||
|
||||
def get_settings_sample(self) -> dict[str, Any]:
|
||||
try:
|
||||
with open(self.settings_sample_path, 'r') as file:
|
||||
return json.load(file)
|
||||
except Exception as e:
|
||||
print(f"Error while reading settings sample at '{self.settings_sample_path}': {e}")
|
||||
raise e
|
||||
|
||||
@overload
|
||||
def get(self, key: str) -> Any: ...
|
||||
|
||||
@overload
|
||||
def get(self, key: None = None) -> dict[str, Any]: ...
|
||||
|
||||
def get(self, key: Union[str, None] = None) -> Union[dict[str, Any], Any]:
|
||||
"""
|
||||
Get the settings
|
||||
:param key: The key to get from the settings
|
||||
"""
|
||||
try:
|
||||
if not os.path.exists(self.settings_path):
|
||||
self.clear()
|
||||
|
||||
with open(self.settings_path, 'r') as file:
|
||||
settings = json.load(file)
|
||||
|
||||
if key is not None:
|
||||
return settings[key]
|
||||
|
||||
return settings
|
||||
except Exception as e:
|
||||
print(f"Error while reading settings at '{self.settings_path}': {e}")
|
||||
raise e
|
||||
|
||||
@overload
|
||||
def set(self, key_or_settings: dict[str, Any]) -> dict[str, Any]: ...
|
||||
|
||||
@overload
|
||||
def set(self, key_or_settings: str, value: Any) -> dict[str, Any]: ...
|
||||
|
||||
def set(self, key_or_settings: Union[str, dict[str, Any]], value: Any = None) -> dict[str, Any]:
|
||||
"""
|
||||
Set the settings
|
||||
:param key_or_settings: The key to set or the settings to set
|
||||
:param value: The value to set
|
||||
"""
|
||||
try:
|
||||
settings = self.get()
|
||||
|
||||
if isinstance(key_or_settings, dict):
|
||||
new_settings = key_or_settings
|
||||
else:
|
||||
new_settings = {**settings, key_or_settings: value}
|
||||
|
||||
with open(self.settings_path, 'w') as file:
|
||||
json.dump(new_settings, file, indent=2)
|
||||
|
||||
return new_settings
|
||||
except Exception as e:
|
||||
print(f"Error while writing settings at '{self.settings_path}': {e}")
|
||||
raise e
|
||||
@@ -0,0 +1,49 @@
|
||||
from typing import Dict, Any, Optional, Union, Literal, TypedDict
|
||||
|
||||
|
||||
class NLUResultSentiment(TypedDict):
|
||||
vote: Optional[Union[Literal['positive'], Literal['neutral'], Literal['negative']]]
|
||||
score: Optional[float]
|
||||
|
||||
|
||||
class ExtraContextData(TypedDict):
|
||||
lang: str
|
||||
sentiment: str
|
||||
date: str
|
||||
time: str
|
||||
timestamp: int
|
||||
date_time: str
|
||||
week_day: str
|
||||
|
||||
|
||||
class ActionParams(TypedDict):
|
||||
lang: str
|
||||
utterance: str
|
||||
current_entities: list[Any]
|
||||
entities: list[Any]
|
||||
current_resolvers: list[Any]
|
||||
resolvers: list[Any]
|
||||
slots: Dict[str, Any]
|
||||
extra_context_data: ExtraContextData
|
||||
|
||||
|
||||
AnswerData = Optional[Union[Dict[str, Union[str, int]], None]]
|
||||
|
||||
|
||||
class Answer(TypedDict):
|
||||
key: Optional[str]
|
||||
widget: Optional[Any]
|
||||
data: Optional[AnswerData]
|
||||
core: Optional[Dict[str, Any]]
|
||||
|
||||
|
||||
class AnswerInput(TypedDict, total=False):
|
||||
key: Optional[str]
|
||||
widget: Any
|
||||
data: Optional[AnswerData]
|
||||
core: Dict[str, Any]
|
||||
|
||||
|
||||
class AnswerConfig(TypedDict):
|
||||
text: str
|
||||
speech: str
|
||||
@@ -0,0 +1,4 @@
|
||||
class Widget:
|
||||
def __init__(self, options):
|
||||
self.type = type(self).__name__
|
||||
self.options = options
|
||||
@@ -1,21 +1,24 @@
|
||||
from cx_Freeze import setup, Executable
|
||||
import requests.certs
|
||||
|
||||
from version import __version__
|
||||
|
||||
options = {
|
||||
'build_exe': {
|
||||
# Add common dependencies for skills
|
||||
# Add common dependencies for skills
|
||||
'includes': [
|
||||
'bs4',
|
||||
'pytube'
|
||||
]
|
||||
}
|
||||
'bs4',
|
||||
'requests',
|
||||
'timeit'
|
||||
],
|
||||
'include_files': [(requests.certs.where(), 'cacert.pem')]
|
||||
}
|
||||
}
|
||||
|
||||
executables = [
|
||||
Executable(
|
||||
script='bridges/python/src/main.py',
|
||||
target_name='leon-python-bridge'
|
||||
Executable(
|
||||
script='bridges/python/src/main.py',
|
||||
target_name='leon-python-bridge'
|
||||
)
|
||||
]
|
||||
|
||||
|
||||
@@ -1,148 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
from json import loads, dumps
|
||||
from pathlib import Path
|
||||
from random import choice
|
||||
from vars import useragent
|
||||
from tinydb import TinyDB, Query, table, operations
|
||||
from time import sleep
|
||||
import sys
|
||||
import os
|
||||
import sqlite3
|
||||
import requests
|
||||
import re
|
||||
|
||||
dirname = os.path.dirname(os.path.realpath(__file__))
|
||||
|
||||
intent_object_path = sys.argv[1]
|
||||
codes = []
|
||||
|
||||
intent_obj_file = open(intent_object_path, 'r', encoding = 'utf8')
|
||||
intent_obj = loads(intent_obj_file.read())
|
||||
intent_obj_file.close()
|
||||
|
||||
def get_intent_obj():
|
||||
"""Return intent object"""
|
||||
|
||||
return intent_obj
|
||||
|
||||
def translate(key, dict = { }):
|
||||
"""Pickup the language file according to the cmd arg
|
||||
and return the value according to the params"""
|
||||
|
||||
# "Temporize" for the data buffer ouput on the core
|
||||
sleep(0.1)
|
||||
|
||||
output = ''
|
||||
variables = { }
|
||||
|
||||
file = open(os.path.join(os.getcwd(), 'skills', intent_obj['domain'], intent_obj['skill'], 'config', intent_obj['lang'] + '.json'), 'r', encoding = 'utf8')
|
||||
obj = loads(file.read())
|
||||
file.close()
|
||||
|
||||
# In case the key is a raw answer
|
||||
if key not in obj['answers']:
|
||||
return key
|
||||
|
||||
prop = obj['answers'][key]
|
||||
if 'variables' in obj:
|
||||
variables = obj['variables']
|
||||
if isinstance(prop, list):
|
||||
output = choice(prop)
|
||||
else:
|
||||
output = prop
|
||||
|
||||
if dict:
|
||||
for key in dict:
|
||||
output = output.replace('%' + key + '%', str(dict[key]))
|
||||
|
||||
if variables:
|
||||
for key in variables:
|
||||
output = output.replace('%' + key + '%', str(variables[key]))
|
||||
|
||||
return output
|
||||
|
||||
def output(type, content = '', core = { }):
|
||||
"""Communicate with the core"""
|
||||
|
||||
if isinstance(content, dict):
|
||||
speech = translate(content['key'], content['data'])
|
||||
codes.append(content['key'])
|
||||
else:
|
||||
content = str(content)
|
||||
speech = translate(content)
|
||||
codes.append(content)
|
||||
|
||||
print(dumps({
|
||||
'domain': intent_obj['domain'],
|
||||
'skill': intent_obj['skill'],
|
||||
'action': intent_obj['action'],
|
||||
'lang': intent_obj['lang'],
|
||||
'utterance': intent_obj['utterance'],
|
||||
'entities': intent_obj['entities'],
|
||||
'slots': intent_obj['slots'],
|
||||
'output': {
|
||||
'type': type,
|
||||
'codes': codes,
|
||||
'speech': speech,
|
||||
'core': core,
|
||||
'options': config('options')
|
||||
}
|
||||
}))
|
||||
|
||||
if (type == 'inter'):
|
||||
sys.stdout.flush()
|
||||
|
||||
def http(method, url, headers = None):
|
||||
"""Send HTTP request with the Leon user agent"""
|
||||
|
||||
session = requests.Session()
|
||||
session.headers.update({ 'User-Agent': useragent, 'Cache-Control': 'no-cache' })
|
||||
|
||||
if headers != None:
|
||||
session.headers.update(headers)
|
||||
|
||||
return session.request(method, url)
|
||||
|
||||
def config(key):
|
||||
"""Get a skill configuration value"""
|
||||
|
||||
file = open(os.path.join(os.getcwd(), 'skills', intent_obj['domain'], intent_obj['skill'], 'src/config.json'), 'r', encoding = 'utf8')
|
||||
obj = loads(file.read())
|
||||
file.close()
|
||||
|
||||
return obj['configurations'][key]
|
||||
|
||||
def create_dl_dir():
|
||||
"""Create the downloads folder of a current skill"""
|
||||
|
||||
dl_dir = os.path.join(os.getcwd(), 'downloads')
|
||||
# dl_dir = os.path.dirname(os.path.realpath(__file__)) + '/../../../../downloads/'
|
||||
skill_dl_dir = os.path.join(dl_dir, intent_obj['domain'], intent_obj['skill'])
|
||||
|
||||
Path(skill_dl_dir).mkdir(parents = True, exist_ok = True)
|
||||
|
||||
return skill_dl_dir
|
||||
|
||||
def db(db_type = 'tinydb'):
|
||||
"""Create a new dedicated database
|
||||
for a specific skill"""
|
||||
|
||||
if db_type == 'tinydb':
|
||||
ext = '.json' if os.environ.get('LEON_NODE_ENV') != 'testing' else '.spec.json'
|
||||
db = TinyDB(os.path.join(os.getcwd(), 'skills', intent_obj['domain'], intent_obj['skill'], 'memory/db' + ext))
|
||||
return {
|
||||
'db': db,
|
||||
'query': Query,
|
||||
'table': table,
|
||||
'operations': operations
|
||||
}
|
||||
|
||||
def get_table(slug):
|
||||
"""Get a table from a specific skill"""
|
||||
|
||||
domain, skill, table = slug.split('.')
|
||||
ext = '.json' if os.environ.get('LEON_NODE_ENV') != 'testing' else '.spec.json'
|
||||
db = TinyDB(os.path.join(os.getcwd(), 'skills', domain, skill, 'memory/db' + ext))
|
||||
return db.table(table)
|
||||
@@ -1,12 +0,0 @@
|
||||
#!/usr/bin/env python
|
||||
# -*- coding:utf-8 -*-
|
||||
|
||||
import sys
|
||||
import os
|
||||
from json import loads
|
||||
|
||||
packagejsonfile = open(os.path.join(os.getcwd(), 'package.json'), 'r', encoding = 'utf8')
|
||||
packagejson = loads(packagejsonfile.read())
|
||||
packagejsonfile.close()
|
||||
|
||||
useragent = 'Leon-Personal-Assistant/' + packagejson['version']
|
||||
@@ -1 +1 @@
|
||||
__version__ = '1.0.0'
|
||||
__version__ = '1.2.0'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/voice-config-schemas/amazon.json",
|
||||
"credentials": {
|
||||
"accessKeyId": "",
|
||||
"secretAccessKey": ""
|
||||
|
||||
@@ -1,12 +1,13 @@
|
||||
{
|
||||
"type": "",
|
||||
"$schema": "../../../schemas/voice-config-schemas/google-cloud.json",
|
||||
"type": "service_account",
|
||||
"project_id": "",
|
||||
"private_key_id": "",
|
||||
"private_key": "",
|
||||
"client_email": "",
|
||||
"client_email": "example@iam.gserviceaccount.com",
|
||||
"client_id": "",
|
||||
"auth_uri": "",
|
||||
"token_uri": "",
|
||||
"auth_provider_x509_cert_url": "",
|
||||
"client_x509_cert_url": ""
|
||||
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
|
||||
"token_uri": "https://accounts.google.com/o/oauth2/token",
|
||||
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
|
||||
"client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/voice-config-schemas/watson-stt.json",
|
||||
"apikey": "",
|
||||
"url": ""
|
||||
"url": "https://stream.watsonplatform.net/speech-to-text/api"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/voice-config-schemas/watson-tts.json",
|
||||
"apikey": "",
|
||||
"url": ""
|
||||
"url": "https://stream.watsonplatform.net/text-to-speech/api"
|
||||
}
|
||||
|
||||
@@ -1,21 +1,15 @@
|
||||
{
|
||||
"$schema": "../../../schemas/global-data/global-answers.json",
|
||||
"answers": {
|
||||
"success": {},
|
||||
"errors": {
|
||||
"not_found": "Sorry, it seems I cannot find that",
|
||||
"nlu": "It might come from my natural language understanding, the error returned is: \"%error%\""
|
||||
},
|
||||
"synchronizer": {
|
||||
"syncing_direct": "I will now synchronize the downloaded content on your current device. Don't worry, I will let you know once I'm done",
|
||||
"synced_direct": "The new content has been synchronized on your device",
|
||||
"syncing_google_drive": "I will now synchronize the downloaded content on Google Drive. Don't worry, I will let you know once I'm done",
|
||||
"synced_google_drive": "The new content is now available on Google Drive"
|
||||
"not_found": "Sorry, it seems I cannot find that"
|
||||
},
|
||||
"random_errors": [
|
||||
"Sorry, there is a problem with my system",
|
||||
"Sorry, I don't work correctly",
|
||||
"Sorry, you need to fix me",
|
||||
"Sorry, I cannot do that because I'm broken"
|
||||
"Sorry, there is a problem with my system. Please check my logs for further details",
|
||||
"Sorry, I don't work correctly. Please look at my logs for more information",
|
||||
"Sorry, you need to fix me. Please take a look at my logs for further information",
|
||||
"Sorry, I cannot do that because I'm broken. Please check my logs for further details"
|
||||
],
|
||||
"random_skill_errors": [
|
||||
"Sorry, it seems I have a problem with the \"%skill_name%\" skill from the \"%domain_name%\" domain",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/global-data/global-entity.json",
|
||||
"options": {
|
||||
"red": {
|
||||
"synonyms": ["red"],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/global-data/global-entity.json",
|
||||
"options": {
|
||||
"LOW": {
|
||||
"synonyms": ["low"]
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/global-data/global-entity.json",
|
||||
"options": {
|
||||
"Alexa": {
|
||||
"synonyms": ["Alexa"],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/global-data/global-resolver.json",
|
||||
"name": "affirmation_denial",
|
||||
"intents": {
|
||||
"affirmation": {
|
||||
@@ -22,7 +23,14 @@
|
||||
"That works",
|
||||
"Go ahead",
|
||||
"Why not",
|
||||
"Please"
|
||||
"Please",
|
||||
"Absolutely",
|
||||
"Precisely",
|
||||
"Spot on",
|
||||
"Undoubtedly",
|
||||
"Certainly",
|
||||
"Without a doubt",
|
||||
"Definitely"
|
||||
],
|
||||
"value": true
|
||||
},
|
||||
@@ -36,7 +44,10 @@
|
||||
"No thanks",
|
||||
"No I'm fine",
|
||||
"Hell no",
|
||||
"Please do not"
|
||||
"Please do not",
|
||||
"I disagree",
|
||||
"Negative",
|
||||
"Not at all"
|
||||
],
|
||||
"value": false
|
||||
}
|
||||
|
||||
@@ -1,21 +1,16 @@
|
||||
{
|
||||
"$schema": "../../../schemas/global-data/global-answers.json",
|
||||
"answers": {
|
||||
"success": {},
|
||||
"errors": {
|
||||
"not_found": "Désolé, il semblerait que je n'arrive pas à trouver ça",
|
||||
"nlu": "L'erreur semble provenir de ma compréhension de langage naturel. Voici plus de détails au sujet de cette dernière : \"%error%\""
|
||||
},
|
||||
"synchronizer": {
|
||||
"syncing_direct": "Je vais maintenant synchroniser le contenu téléchargé sur votre appareil actuel. Ne vous inquiétez pas, je vous préviendrai lorsque j'aurai terminé",
|
||||
"synced_direct": "Le nouveau contenu a été synchronisé sur votre appareil",
|
||||
"syncing_google_drive": "Je vais maintenant synchroniser le contenu téléchargé sur Google Drive. Ne vous inquiétez pas, je vous préviendrai lorsque j'aurai terminé",
|
||||
"synced_google_drive": "Le nouveau contenu est maintenant disponible sur Google Drive"
|
||||
},
|
||||
"random_errors": [
|
||||
"Désolé, il y a un problème avec mon système",
|
||||
"Désolé, je ne fonctionne pas correctement",
|
||||
"Désolé, vous devez me réparer",
|
||||
"Désolé, je ne peux aboutir à votre demande parce que je suis cassé"
|
||||
"Désolé, il y a un problème avec mon système. Veuillez consulter mes logs pour plus de détails",
|
||||
"Désolé, je ne fonctionne pas correctement. Merci de regarder mes logs pour plus d'information",
|
||||
"Désolé, vous devez me réparer. Veuillez vérifier mes logs pour en savoir plus",
|
||||
"Désolé, je ne peux aboutir à votre demande parce que je suis cassé. Regardez mes logs pour plus de détails"
|
||||
],
|
||||
"random_skill_errors": [
|
||||
"Désolé, il semblerait y avoir un problème avec le skill \"%skill_name%\" du domaine \"%domain_name%\"",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/global-data/global-entity.json",
|
||||
"options": {
|
||||
"rouge": {
|
||||
"synonyms": ["rouge"],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/global-data/global-entity.json",
|
||||
"options": {
|
||||
"bas": {
|
||||
"synonyms": ["bas", "basse"],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/global-data/global-entity.json",
|
||||
"options": {
|
||||
"Alexa": {
|
||||
"synonyms": ["Alexa"],
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/global-data/global-resolver.json",
|
||||
"name": "affirmation_denial",
|
||||
"intents": {
|
||||
"affirmation": {
|
||||
|
||||
+157
-122
@@ -1,122 +1,5 @@
|
||||
{
|
||||
"endpoints": [
|
||||
{
|
||||
"method": "POST",
|
||||
"route": "/api/action/games/akinator/choose_thematic",
|
||||
"params": ["thematic"],
|
||||
"entitiesType": "trim"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/games/akinator/setup",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/games/akinator/guess",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/games/akinator/retry",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/games/guess_the_number/setup",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/games/guess_the_number/guess",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/games/guess_the_number/replay",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/games/rochambeau/start",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"route": "/api/action/games/rochambeau/play",
|
||||
"params": ["handsign"],
|
||||
"entitiesType": "trim"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/games/rochambeau/rematch",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/color/favorite_color",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/color/why",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/color/color_hexadecimal",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/good_bye/run",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/greeting/run",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/introduction/introduce_leon",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/introduction/gather_basic_info",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/introduction/remember",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/joke/run",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/meaning_of_life/run",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/partner_assistant/run",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/random_number/run",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/welcome/run",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"route": "/api/action/news/github_trends/run",
|
||||
@@ -185,6 +68,163 @@
|
||||
"route": "/api/action/social_communication/mbti/quiz",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"route": "/api/action/games/akinator/choose_thematic",
|
||||
"params": ["thematic"],
|
||||
"entitiesType": "trim"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/games/akinator/setup",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/games/akinator/guess",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/games/akinator/retry",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/games/guess_the_number/setup",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/games/guess_the_number/guess",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/games/guess_the_number/replay",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/games/rochambeau/start",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "POST",
|
||||
"route": "/api/action/games/rochambeau/play",
|
||||
"params": ["handsign"],
|
||||
"entitiesType": "trim"
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/games/rochambeau/rematch",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/age/run",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/color/favorite_color",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/color/why",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/color/color_hexadecimal",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/good_bye/run",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/greeting/run",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/help/help",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/introduction/introduce_leon",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/introduction/gather_basic_info",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/introduction/remember",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/joke/run",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/meaning_of_life/run",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/partner_assistant/run",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/random_number/run",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/leon/welcome/run",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/utilities/date_time/current_date_time",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/utilities/date_time/current_date",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/utilities/date_time/current_time",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/utilities/date_time/current_week_number",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/utilities/date_time/days_countdown",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/utilities/date_time/current_date_time_with_time_zone",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/utilities/have_i_been_pwned/run",
|
||||
@@ -200,11 +240,6 @@
|
||||
"method": "GET",
|
||||
"route": "/api/action/utilities/speed_test/run",
|
||||
"params": []
|
||||
},
|
||||
{
|
||||
"method": "GET",
|
||||
"route": "/api/action/utilities/youtube_downloader/run",
|
||||
"params": []
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -12,6 +12,6 @@ services:
|
||||
tty: true
|
||||
command: 'npm run dev:server && npm run dev:app'
|
||||
volumes:
|
||||
- './:/app'
|
||||
- '/app/node_modules'
|
||||
- './:/home/docker/leon'
|
||||
- '/home/docker/leon/node_modules'
|
||||
network_mode: 'host'
|
||||
|
||||
+11
-10
@@ -3,7 +3,7 @@
|
||||
* You can consider to run this file on a different hardware
|
||||
*/
|
||||
|
||||
const request = require('superagent')
|
||||
const axios = require('axios')
|
||||
const record = require('node-record-lpcm16')
|
||||
const { Detector, Models } = require('@bugsounet/snowboy')
|
||||
const { io } = require('socket.io-client')
|
||||
@@ -21,15 +21,10 @@ socket.on('connect', () => {
|
||||
console.log('Connected to the server')
|
||||
console.log('Waiting for hotword...')
|
||||
})
|
||||
;(async () => {
|
||||
try {
|
||||
await axios.get(`${url}/api/v1/info`)
|
||||
|
||||
request.get(`${url}/api/v1/info`).end((err, res) => {
|
||||
if (err || !res.ok) {
|
||||
if (!err.response) {
|
||||
console.error(`Failed to reach the server: ${err}`)
|
||||
} else {
|
||||
console.error(err.response.error.message)
|
||||
}
|
||||
} else {
|
||||
const models = new Models()
|
||||
|
||||
models.add({
|
||||
@@ -78,5 +73,11 @@ request.get(`${url}/api/v1/info`).end((err, res) => {
|
||||
})
|
||||
|
||||
mic.pipe(detector)
|
||||
} catch (e) {
|
||||
if (!e.response) {
|
||||
console.error(`Failed to reach the server: ${e}`)
|
||||
} else {
|
||||
console.error(e)
|
||||
}
|
||||
}
|
||||
})
|
||||
})()
|
||||
|
||||
Generated
-27191
File diff suppressed because it is too large
Load Diff
+81
-57
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "leon",
|
||||
"version": "1.0.0-beta.8+dev",
|
||||
"version": "1.0.0-beta.10+dev",
|
||||
"description": "Server, skills and web app of the Leon personal assistant",
|
||||
"author": {
|
||||
"name": "Louis Grenard",
|
||||
@@ -40,21 +40,27 @@
|
||||
"postinstall": "ts-node scripts/setup/setup.js",
|
||||
"dev:app": "vite --config app/vite.config.js",
|
||||
"dev:server": "npm run train && npm run generate:skills-endpoints && cross-env LEON_NODE_ENV=development tsc-watch --noClear --onSuccess \"nodemon\"",
|
||||
"dev:server:no-lint": "npm run train && npm run generate:skills-endpoints && cross-env LEON_NODE_ENV=development \"nodemon\"",
|
||||
"wake": "cross-env LEON_HOST=http://localhost LEON_PORT=1337 node hotword/index.js",
|
||||
"delete-dist:server": "shx rm -rf ./server/dist",
|
||||
"clean:python-deps": "shx rm -rf ./bridges/python/src/.venv && npm run postinstall",
|
||||
"prepare": "husky install",
|
||||
"generate:skills-endpoints": "ts-node scripts/generate/run-generate-skills-endpoints.js",
|
||||
"generate:http-api-key": "ts-node scripts/generate/run-generate-http-api-key.js",
|
||||
"generate:json-schemas": "ts-node scripts/generate/run-generate-json-schemas.js",
|
||||
"build": "npm run build:app && npm run build:server",
|
||||
"build:app": "cross-env LEON_NODE_ENV=production ts-node scripts/app/run-build-app.js",
|
||||
"build:server": "npm run delete-dist:server && npm run train && npm run generate:skills-endpoints && tsc && resolve-tspaths && shx rm -rf server/dist/core server/dist/package.json && shx mv -f server/dist/server/src/* server/dist && shx rm -rf server/dist/server && shx mkdir -p server/dist/tmp",
|
||||
"build:server": "npm run delete-dist:server && npm run train && npm run generate:skills-endpoints && tsc --project tsconfig.json && resolve-tspaths && shx rm -rf server/dist/core server/dist/package.json && shx mv -f server/dist/server/src/* server/dist && shx rm -rf server/dist/server && shx mkdir -p server/dist/tmp",
|
||||
"build:nodejs-bridge": "ts-node scripts/build-binaries.js nodejs-bridge",
|
||||
"build:python-bridge": "ts-node scripts/build-binaries.js python-bridge",
|
||||
"build:tcp-server": "ts-node scripts/build-binaries.js tcp-server",
|
||||
"start:tcp-server": "cross-env PIPENV_PIPFILE=tcp_server/src/Pipfile pipenv run python tcp_server/src/main.py",
|
||||
"start": "cross-env LEON_NODE_ENV=production node ./server/dist/index.js",
|
||||
"start": "cross-env LEON_NODE_ENV=production node server/dist/pre-check.js && node server/dist/index.js",
|
||||
"python-bridge": "cross-env PIPENV_PIPFILE=bridges/python/src/Pipfile pipenv run python bridges/python/src/main.py server/src/intent-object.sample.json",
|
||||
"train": "ts-node scripts/train/run-train.js",
|
||||
"prepare-release": "ts-node scripts/release/prepare-release.js",
|
||||
"skill-package": "ts-node scripts/skill-package.js",
|
||||
"pre-release:nodejs-bridge": "ts-node scripts/release/pre-release-binaries.js nodejs-bridge",
|
||||
"pre-release:python-bridge": "ts-node scripts/release/pre-release-binaries.js python-bridge",
|
||||
"pre-release:tcp-server": "ts-node scripts/release/pre-release-binaries.js tcp-server",
|
||||
"check": "ts-node scripts/check.js",
|
||||
@@ -64,65 +70,83 @@
|
||||
"docker:check": "docker run --rm --interactive leon-ai/leon npm run check"
|
||||
},
|
||||
"dependencies": {
|
||||
"@aws-sdk/client-polly": "^3.18.0",
|
||||
"@fastify/static": "^6.5.0",
|
||||
"@ffmpeg-installer/ffmpeg": "^1.1.0",
|
||||
"@ffprobe-installer/ffprobe": "^1.4.1",
|
||||
"@google-cloud/speech": "^4.2.0",
|
||||
"@google-cloud/text-to-speech": "^3.2.1",
|
||||
"@nlpjs/builtin-microsoft": "^4.22.7",
|
||||
"@nlpjs/core-loader": "^4.22.7",
|
||||
"@nlpjs/lang-all": "^4.22.12",
|
||||
"@nlpjs/nlp": "^4.22.17",
|
||||
"archiver": "^5.3.1",
|
||||
"async": "^3.2.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"dayjs": "^1.11.5",
|
||||
"dotenv": "^10.0.0",
|
||||
"execa": "^5.0.0",
|
||||
"fastify": "^4.5.3",
|
||||
"fluent-ffmpeg": "^2.1.2",
|
||||
"googleapis": "^67.1.1",
|
||||
"ibm-watson": "^6.1.1",
|
||||
"@aws-sdk/client-polly": "3.18.0",
|
||||
"@fastify/static": "6.9.0",
|
||||
"@ffmpeg-installer/ffmpeg": "1.1.0",
|
||||
"@ffprobe-installer/ffprobe": "1.4.1",
|
||||
"@google-cloud/speech": "4.2.0",
|
||||
"@google-cloud/text-to-speech": "3.2.1",
|
||||
"@nlpjs/builtin-microsoft": "4.22.7",
|
||||
"@nlpjs/core-loader": "4.22.7",
|
||||
"@nlpjs/lang-all": "4.22.12",
|
||||
"@nlpjs/nlp": "4.22.17",
|
||||
"@segment/ajv-human-errors": "2.1.2",
|
||||
"@sinclair/typebox": "0.25.8",
|
||||
"ajv": "8.11.0",
|
||||
"ajv-formats": "2.1.1",
|
||||
"archiver": "5.3.1",
|
||||
"async": "3.2.4",
|
||||
"axios": "1.1.2",
|
||||
"cross-env": "7.0.3",
|
||||
"dayjs": "1.11.5",
|
||||
"dotenv": "10.0.0",
|
||||
"execa": "5.0.0",
|
||||
"extract-zip": "2.0.1",
|
||||
"fastify": "4.15.0",
|
||||
"fluent-ffmpeg": "2.1.2",
|
||||
"getos": "3.2.1",
|
||||
"googleapis": "67.1.1",
|
||||
"ibm-watson": "6.1.1",
|
||||
"node-wav": "0.0.2",
|
||||
"pretty-bytes": "^5.6.0",
|
||||
"socket.io": "^4.5.2",
|
||||
"socket.io-client": "^4.5.2",
|
||||
"stt": "^1.4.0",
|
||||
"superagent": "^8.0.0",
|
||||
"tree-kill": "^1.2.2"
|
||||
"os-name": "4.0.1",
|
||||
"pretty-bytes": "5.6.0",
|
||||
"pretty-ms": "7.0.1",
|
||||
"react": "18.2.0",
|
||||
"react-dom": "18.2.0",
|
||||
"socket.io": "4.5.2",
|
||||
"socket.io-client": "4.5.2",
|
||||
"stt": "1.4.0",
|
||||
"tree-kill": "1.2.2"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@nlpjs/utils": "^4.24.1",
|
||||
"@swc/core": "^1.2.245",
|
||||
"@tsconfig/node16-strictest": "^1.0.3",
|
||||
"@nlpjs/utils": "4.24.1",
|
||||
"@swc/core": "1.3.14",
|
||||
"@tsconfig/node16-strictest": "1.0.4",
|
||||
"@types/archiver": "5.3.2",
|
||||
"@types/cli-spinner": "0.2.1",
|
||||
"@types/node": "^18.7.13",
|
||||
"@typescript-eslint/eslint-plugin": "^5.36.1",
|
||||
"@typescript-eslint/parser": "^5.36.1",
|
||||
"cli-spinner": "^0.2.10",
|
||||
"@types/fluent-ffmpeg": "2.1.20",
|
||||
"@types/getos": "3.0.1",
|
||||
"@types/node": "18.7.13",
|
||||
"@types/node-wav": "0.0.0",
|
||||
"@types/react": "18.2.6",
|
||||
"@types/react-dom": "18.2.4",
|
||||
"@typescript-eslint/eslint-plugin": "5.55.0",
|
||||
"@typescript-eslint/parser": "5.55.0",
|
||||
"@vercel/ncc": "0.36.1",
|
||||
"@vitejs/plugin-react": "4.0.0",
|
||||
"cli-spinner": "0.2.10",
|
||||
"eslint": "8.22.0",
|
||||
"eslint-config-prettier": "^8.5.0",
|
||||
"eslint-import-resolver-typescript": "^3.5.1",
|
||||
"eslint-plugin-import": "^2.26.0",
|
||||
"eslint-config-prettier": "8.5.0",
|
||||
"eslint-import-resolver-typescript": "3.5.1",
|
||||
"eslint-plugin-import": "2.26.0",
|
||||
"eslint-plugin-unicorn": "43.0.2",
|
||||
"git-changelog": "^2.0.0",
|
||||
"husky": "^7.0.0",
|
||||
"inquirer": "^8.1.0",
|
||||
"jest": "^27.4.7",
|
||||
"jest-canvas-mock": "^2.3.1",
|
||||
"jest-extended": "^2.0.0",
|
||||
"json": "^10.0.0",
|
||||
"lint-staged": "^13.0.3",
|
||||
"nodemon": "^2.0.19",
|
||||
"prettier": "2.7.1",
|
||||
"resolve-tspaths": "^0.7.4",
|
||||
"semver": "^7.3.5",
|
||||
"shx": "^0.3.3",
|
||||
"ts-node": "^10.9.1",
|
||||
"tsc-watch": "^5.0.3",
|
||||
"tsconfig-paths": "^4.1.0",
|
||||
"typescript": "^4.8.2",
|
||||
"vite": "^3.0.9"
|
||||
"git-changelog": "2.0.0",
|
||||
"husky": "7.0.0",
|
||||
"inquirer": "8.1.0",
|
||||
"jest": "27.4.7",
|
||||
"jest-canvas-mock": "2.3.1",
|
||||
"jest-extended": "2.0.0",
|
||||
"json": "10.0.0",
|
||||
"lint-staged": "13.0.3",
|
||||
"nodemon": "2.0.19",
|
||||
"prettier": "2.8.7",
|
||||
"resolve-tspaths": "0.8.8",
|
||||
"semver": "7.3.5",
|
||||
"shx": "0.3.3",
|
||||
"ts-node": "10.9.1",
|
||||
"tsc-watch": "6.0.0",
|
||||
"tsconfig-paths": "4.1.2",
|
||||
"typescript": "5.0.2",
|
||||
"vite": "4.3.5"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
{
|
||||
"lang": "en",
|
||||
"domain": "leon",
|
||||
"skill": "age",
|
||||
"action": "run",
|
||||
"utterance": "How old are you?",
|
||||
"slots": {},
|
||||
"entities": [],
|
||||
"current_entities": [],
|
||||
"resolvers": [],
|
||||
"current_resolvers": []
|
||||
}
|
||||
+80
-25
@@ -9,14 +9,18 @@ import {
|
||||
PYTHON_BRIDGE_SRC_PATH,
|
||||
TCP_SERVER_SRC_PATH,
|
||||
BINARIES_FOLDER_NAME,
|
||||
NODEJS_BRIDGE_DIST_PATH,
|
||||
PYTHON_BRIDGE_DIST_PATH,
|
||||
TCP_SERVER_DIST_PATH,
|
||||
NODEJS_BRIDGE_BIN_NAME,
|
||||
PYTHON_BRIDGE_BIN_NAME,
|
||||
TCP_SERVER_BIN_NAME
|
||||
TCP_SERVER_BIN_NAME,
|
||||
NODEJS_BRIDGE_ROOT_PATH
|
||||
} from '@/constants'
|
||||
import { OSTypes } from '@/types'
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
import { LoaderHelper } from '@/helpers/loader-helper'
|
||||
import { OSHelper, OSTypes } from '@/helpers/os-helper'
|
||||
import { SystemHelper } from '@/helpers/system-helper'
|
||||
|
||||
/**
|
||||
* Build binaries for the given OS according to the given build target
|
||||
@@ -28,8 +32,15 @@ import { OSHelper, OSTypes } from '@/helpers/os-helper'
|
||||
|
||||
const BUILD_TARGETS = new Map()
|
||||
|
||||
BUILD_TARGETS.set('nodejs-bridge', {
|
||||
name: 'Node.js bridge',
|
||||
needsPythonEnv: false,
|
||||
distPath: NODEJS_BRIDGE_DIST_PATH,
|
||||
archiveName: `${NODEJS_BRIDGE_BIN_NAME.split('.')[0]}.zip`
|
||||
})
|
||||
BUILD_TARGETS.set('python-bridge', {
|
||||
name: 'Python bridge',
|
||||
needsPythonEnv: true,
|
||||
pipfilePath: path.join(PYTHON_BRIDGE_SRC_PATH, 'Pipfile'),
|
||||
setupFilePath: path.join(PYTHON_BRIDGE_SRC_PATH, 'setup.py'),
|
||||
distPath: PYTHON_BRIDGE_DIST_PATH,
|
||||
@@ -38,6 +49,7 @@ BUILD_TARGETS.set('python-bridge', {
|
||||
})
|
||||
BUILD_TARGETS.set('tcp-server', {
|
||||
name: 'TCP server',
|
||||
needsPythonEnv: true,
|
||||
pipfilePath: path.join(TCP_SERVER_SRC_PATH, 'Pipfile'),
|
||||
setupFilePath: path.join(TCP_SERVER_SRC_PATH, 'setup.py'),
|
||||
distPath: TCP_SERVER_DIST_PATH,
|
||||
@@ -61,21 +73,24 @@ BUILD_TARGETS.set('tcp-server', {
|
||||
|
||||
const {
|
||||
name: buildTarget,
|
||||
needsPythonEnv,
|
||||
pipfilePath,
|
||||
setupFilePath,
|
||||
distPath,
|
||||
archiveName,
|
||||
dotVenvPath
|
||||
} = BUILD_TARGETS.get(givenBuildTarget)
|
||||
const buildPath = path.join(distPath, BINARIES_FOLDER_NAME)
|
||||
const buildPath = needsPythonEnv
|
||||
? path.join(distPath, BINARIES_FOLDER_NAME)
|
||||
: path.join(distPath, 'bin')
|
||||
|
||||
const { type: osType } = OSHelper.getInformation()
|
||||
const { type: osType } = SystemHelper.getInformation()
|
||||
|
||||
/**
|
||||
* Install requirements
|
||||
*/
|
||||
try {
|
||||
if (osType === OSTypes.Linux) {
|
||||
if (needsPythonEnv && osType === OSTypes.Linux) {
|
||||
LogHelper.info('Checking whether the "patchelf" utility can be found...')
|
||||
|
||||
await command('patchelf --version', { shell: true })
|
||||
@@ -91,30 +106,66 @@ BUILD_TARGETS.set('tcp-server', {
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
/**
|
||||
* Build
|
||||
*/
|
||||
try {
|
||||
LogHelper.info(`Building the ${buildTarget}...`)
|
||||
LogHelper.info(`Building the ${buildTarget}...`)
|
||||
|
||||
// Required environment variables to set up
|
||||
process.env.PIPENV_PIPFILE = pipfilePath
|
||||
process.env.PIPENV_VENV_IN_PROJECT = true
|
||||
if (needsPythonEnv) {
|
||||
/**
|
||||
* Build for binaries requiring a Python environment
|
||||
*/
|
||||
try {
|
||||
// Required environment variables to set up
|
||||
process.env.PIPENV_PIPFILE = pipfilePath
|
||||
process.env.PIPENV_VENV_IN_PROJECT = true
|
||||
|
||||
await command(
|
||||
`pipenv run python ${setupFilePath} build --build-exe ${buildPath}`,
|
||||
{
|
||||
await command(
|
||||
`pipenv run python ${setupFilePath} build --build-exe ${buildPath}`,
|
||||
{
|
||||
shell: true,
|
||||
stdio: 'inherit'
|
||||
}
|
||||
)
|
||||
|
||||
LogHelper.success(`The ${buildTarget} has been built`)
|
||||
} catch (e) {
|
||||
LogHelper.error(
|
||||
`An error occurred while building the ${buildTarget}. Try to delete the ${dotVenvPath} folder, run the setup command then build again: ${e}`
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
} else {
|
||||
/**
|
||||
* Build for binaries not requiring a Python environment
|
||||
*/
|
||||
try {
|
||||
const distBinPath = path.join(NODEJS_BRIDGE_DIST_PATH, 'bin')
|
||||
const distMainFilePath = path.join(distBinPath, 'index.js')
|
||||
const distRenamedMainFilePath = path.join(
|
||||
distBinPath,
|
||||
NODEJS_BRIDGE_BIN_NAME
|
||||
)
|
||||
|
||||
await fs.promises.rm(buildPath, { recursive: true, force: true })
|
||||
|
||||
const inputMainFilePath = path.join(
|
||||
NODEJS_BRIDGE_ROOT_PATH,
|
||||
'src',
|
||||
'main.ts'
|
||||
)
|
||||
|
||||
await command(`ncc build ${inputMainFilePath} --out ${distBinPath}`, {
|
||||
shell: true,
|
||||
stdio: 'inherit'
|
||||
}
|
||||
)
|
||||
})
|
||||
|
||||
LogHelper.success(`The ${buildTarget} has been built`)
|
||||
} catch (e) {
|
||||
LogHelper.error(
|
||||
`An error occurred while building the ${buildTarget}. Try to delete the ${dotVenvPath} folder, run the setup command then build again: ${e}`
|
||||
)
|
||||
process.exit(1)
|
||||
await fs.promises.rename(distMainFilePath, distRenamedMainFilePath)
|
||||
|
||||
LogHelper.success(`The ${buildTarget} has been built`)
|
||||
} catch (e) {
|
||||
LogHelper.error(
|
||||
`An error occurred while building the ${buildTarget}: ${e}`
|
||||
)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -142,7 +193,11 @@ BUILD_TARGETS.set('tcp-server', {
|
||||
|
||||
archive.pipe(output)
|
||||
|
||||
archive.directory(buildPath, BINARIES_FOLDER_NAME)
|
||||
if (needsPythonEnv) {
|
||||
archive.directory(buildPath, BINARIES_FOLDER_NAME)
|
||||
} else {
|
||||
archive.directory(buildPath, 'bin')
|
||||
}
|
||||
|
||||
await archive.finalize()
|
||||
})()
|
||||
|
||||
+2
-2
@@ -1,7 +1,7 @@
|
||||
import execa from 'execa'
|
||||
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
import { OSHelper } from '@/helpers/os-helper'
|
||||
import { SystemHelper } from '@/helpers/system-helper'
|
||||
|
||||
/**
|
||||
* Check OS environment
|
||||
@@ -10,7 +10,7 @@ export default () =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
LogHelper.info('Checking OS environment...')
|
||||
|
||||
const info = OSHelper.getInformation()
|
||||
const info = SystemHelper.getInformation()
|
||||
|
||||
if (info.type === 'windows') {
|
||||
LogHelper.error('Voice offline mode is not available on Windows')
|
||||
|
||||
+172
-80
@@ -1,4 +1,5 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import os from 'node:os'
|
||||
import { spawn } from 'node:child_process'
|
||||
|
||||
@@ -6,15 +7,22 @@ import dotenv from 'dotenv'
|
||||
import { command } from 'execa'
|
||||
import semver from 'semver'
|
||||
import kill from 'tree-kill'
|
||||
import request from 'superagent'
|
||||
import axios from 'axios'
|
||||
import osName from 'os-name'
|
||||
import getos from 'getos'
|
||||
|
||||
import { version } from '@@/package.json'
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
import { SystemHelper } from '@/helpers/system-helper'
|
||||
import {
|
||||
MINIMUM_REQUIRED_RAM,
|
||||
LEON_VERSION,
|
||||
NODEJS_BRIDGE_BIN_PATH,
|
||||
PYTHON_BRIDGE_BIN_PATH,
|
||||
TCP_SERVER_BIN_PATH,
|
||||
TCP_SERVER_VERSION,
|
||||
PYTHON_BRIDGE_VERSION
|
||||
NODEJS_BRIDGE_VERSION,
|
||||
PYTHON_BRIDGE_VERSION,
|
||||
INSTANCE_ID
|
||||
} from '@/constants'
|
||||
|
||||
dotenv.config()
|
||||
@@ -26,7 +34,7 @@ dotenv.config()
|
||||
;(async () => {
|
||||
try {
|
||||
const nodeMinRequiredVersion = '16'
|
||||
const npmMinRequiredVersion = '5'
|
||||
const npmMinRequiredVersion = '8'
|
||||
const flitePath = 'bin/flite/flite'
|
||||
const coquiLanguageModelPath = 'bin/coqui/huge-vocabulary.scorer'
|
||||
const amazonPath = 'core/config/voice/amazon.json'
|
||||
@@ -38,34 +46,6 @@ dotenv.config()
|
||||
const skillsResolversNlpModelPath =
|
||||
'core/data/models/leon-skills-resolvers-model.nlp'
|
||||
const mainNlpModelPath = 'core/data/models/leon-main-model.nlp'
|
||||
const pastebinData = {
|
||||
leonVersion: null,
|
||||
environment: {
|
||||
osDetails: null,
|
||||
nodeVersion: null,
|
||||
npmVersion: null
|
||||
},
|
||||
nlpModels: {
|
||||
globalResolversModelState: null,
|
||||
skillsResolversModelState: null,
|
||||
mainModelState: null
|
||||
},
|
||||
pythonBridge: {
|
||||
version: null,
|
||||
executionTime: null,
|
||||
command: null,
|
||||
output: null,
|
||||
error: null
|
||||
},
|
||||
tcpServer: {
|
||||
version: null,
|
||||
startTime: null,
|
||||
command: null,
|
||||
output: null,
|
||||
error: null
|
||||
},
|
||||
report: null
|
||||
}
|
||||
const report = {
|
||||
can_run: { title: 'Run', type: 'error', v: true },
|
||||
can_run_skill: { title: 'Run skills', type: 'error', v: true },
|
||||
@@ -111,6 +91,42 @@ dotenv.config()
|
||||
v: true
|
||||
}
|
||||
}
|
||||
let reportDataInput = {
|
||||
leonVersion: null,
|
||||
instanceID: INSTANCE_ID || null,
|
||||
environment: {
|
||||
osDetails: null,
|
||||
nodeVersion: null,
|
||||
npmVersion: null
|
||||
},
|
||||
nlpModels: {
|
||||
globalResolversModelState: null,
|
||||
skillsResolversModelState: null,
|
||||
mainModelState: null
|
||||
},
|
||||
nodeJSBridge: {
|
||||
version: null,
|
||||
executionTime: null,
|
||||
command: null,
|
||||
output: null,
|
||||
error: null
|
||||
},
|
||||
pythonBridge: {
|
||||
version: null,
|
||||
executionTime: null,
|
||||
command: null,
|
||||
output: null,
|
||||
error: null
|
||||
},
|
||||
tcpServer: {
|
||||
version: null,
|
||||
startTime: null,
|
||||
command: null,
|
||||
output: null,
|
||||
error: null
|
||||
},
|
||||
report: null
|
||||
}
|
||||
|
||||
LogHelper.title('Checking')
|
||||
|
||||
@@ -119,24 +135,50 @@ dotenv.config()
|
||||
*/
|
||||
|
||||
LogHelper.info('Leon version')
|
||||
LogHelper.success(`${version}\n`)
|
||||
pastebinData.leonVersion = version
|
||||
LogHelper.success(`${LEON_VERSION}\n`)
|
||||
reportDataInput.leonVersion = LEON_VERSION
|
||||
|
||||
/**
|
||||
* Environment checking
|
||||
*/
|
||||
|
||||
LogHelper.info('OS')
|
||||
LogHelper.info('Environment')
|
||||
|
||||
const osInfo = {
|
||||
type: os.type(),
|
||||
platform: os.platform(),
|
||||
arch: os.arch(),
|
||||
cpus: os.cpus().length,
|
||||
release: os.release()
|
||||
release: os.release(),
|
||||
osName: osName(),
|
||||
distro: null
|
||||
}
|
||||
LogHelper.success(`${JSON.stringify(osInfo)}\n`)
|
||||
pastebinData.environment.osDetails = osInfo
|
||||
const totalRAMInGB = SystemHelper.getTotalRAM()
|
||||
const freeRAMInGB = SystemHelper.getFreeRAM()
|
||||
|
||||
if (Math.round(freeRAMInGB) < MINIMUM_REQUIRED_RAM) {
|
||||
report.can_run.v = false
|
||||
LogHelper.error(
|
||||
`Free RAM: ${freeRAMInGB} GB | Total RAM: ${totalRAMInGB} GB. Leon needs at least ${MINIMUM_REQUIRED_RAM} GB of RAM`
|
||||
)
|
||||
} else {
|
||||
LogHelper.success(
|
||||
`Free RAM: ${freeRAMInGB} GB | Total RAM: ${totalRAMInGB} GB`
|
||||
)
|
||||
}
|
||||
|
||||
if (osInfo.platform === 'linux') {
|
||||
getos((e, os) => {
|
||||
osInfo.distro = os
|
||||
LogHelper.success(`${JSON.stringify(osInfo)}\n`)
|
||||
})
|
||||
} else {
|
||||
LogHelper.success(`${JSON.stringify(osInfo)}\n`)
|
||||
}
|
||||
|
||||
reportDataInput.environment.osDetails = osInfo
|
||||
reportDataInput.environment.totalRAMInGB = totalRAMInGB
|
||||
reportDataInput.environment.freeRAMInGB = freeRAMInGB
|
||||
;(
|
||||
await Promise.all([
|
||||
command('node --version', { shell: true }),
|
||||
@@ -168,40 +210,79 @@ dotenv.config()
|
||||
} else {
|
||||
LogHelper.success(`${p.stdout}\n`)
|
||||
if (p.command.includes('node --version')) {
|
||||
pastebinData.environment.nodeVersion = p.stdout
|
||||
reportDataInput.environment.nodeVersion = p.stdout
|
||||
} else if (p.command.includes('npm --version')) {
|
||||
pastebinData.environment.npmVersion = p.stdout
|
||||
reportDataInput.environment.npmVersion = p.stdout
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
/**
|
||||
* Skill execution checking
|
||||
* Skill execution checking with Node.js bridge
|
||||
*/
|
||||
|
||||
LogHelper.success(`Python bridge version: ${PYTHON_BRIDGE_VERSION}`)
|
||||
pastebinData.pythonBridge.version = PYTHON_BRIDGE_VERSION
|
||||
LogHelper.success(`Node.js bridge version: ${NODEJS_BRIDGE_VERSION}`)
|
||||
reportDataInput.nodeJSBridge.version = NODEJS_BRIDGE_VERSION
|
||||
LogHelper.info('Executing a skill...')
|
||||
|
||||
try {
|
||||
const executionStart = Date.now()
|
||||
const p = await command(
|
||||
`${PYTHON_BRIDGE_BIN_PATH} scripts/assets/intent-object.json`,
|
||||
`${NODEJS_BRIDGE_BIN_PATH} "${path.join(
|
||||
process.cwd(),
|
||||
'scripts',
|
||||
'assets',
|
||||
'nodejs-bridge-intent-object.json'
|
||||
)}"`,
|
||||
{ shell: true }
|
||||
)
|
||||
const executionEnd = Date.now()
|
||||
const executionTime = executionEnd - executionStart
|
||||
LogHelper.info(p.command)
|
||||
pastebinData.pythonBridge.command = p.command
|
||||
reportDataInput.nodeJSBridge.command = p.command
|
||||
LogHelper.success(p.stdout)
|
||||
pastebinData.pythonBridge.output = p.stdout
|
||||
reportDataInput.nodeJSBridge.output = p.stdout
|
||||
LogHelper.info(`Skill execution time: ${executionTime}ms\n`)
|
||||
pastebinData.pythonBridge.executionTime = `${executionTime}ms`
|
||||
reportDataInput.nodeJSBridge.executionTime = `${executionTime}ms`
|
||||
} catch (e) {
|
||||
LogHelper.info(e.command)
|
||||
report.can_run_skill.v = false
|
||||
LogHelper.error(`${e}\n`)
|
||||
pastebinData.pythonBridge.error = JSON.stringify(e)
|
||||
reportDataInput.nodeJSBridge.error = JSON.stringify(e)
|
||||
}
|
||||
|
||||
/**
|
||||
* Skill execution checking with Python bridge
|
||||
*/
|
||||
|
||||
LogHelper.success(`Python bridge version: ${PYTHON_BRIDGE_VERSION}`)
|
||||
reportDataInput.pythonBridge.version = PYTHON_BRIDGE_VERSION
|
||||
LogHelper.info('Executing a skill...')
|
||||
|
||||
try {
|
||||
const executionStart = Date.now()
|
||||
const p = await command(
|
||||
`${PYTHON_BRIDGE_BIN_PATH} "${path.join(
|
||||
process.cwd(),
|
||||
'scripts',
|
||||
'assets',
|
||||
'python-bridge-intent-object.json'
|
||||
)}"`,
|
||||
{ shell: true }
|
||||
)
|
||||
const executionEnd = Date.now()
|
||||
const executionTime = executionEnd - executionStart
|
||||
LogHelper.info(p.command)
|
||||
reportDataInput.pythonBridge.command = p.command
|
||||
LogHelper.success(p.stdout)
|
||||
reportDataInput.pythonBridge.output = p.stdout
|
||||
LogHelper.info(`Skill execution time: ${executionTime}ms\n`)
|
||||
reportDataInput.pythonBridge.executionTime = `${executionTime}ms`
|
||||
} catch (e) {
|
||||
LogHelper.info(e.command)
|
||||
report.can_run_skill.v = false
|
||||
LogHelper.error(`${e}\n`)
|
||||
reportDataInput.pythonBridge.error = JSON.stringify(e)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -209,16 +290,19 @@ dotenv.config()
|
||||
*/
|
||||
|
||||
LogHelper.success(`TCP server version: ${TCP_SERVER_VERSION}`)
|
||||
pastebinData.tcpServer.version = TCP_SERVER_VERSION
|
||||
reportDataInput.tcpServer.version = TCP_SERVER_VERSION
|
||||
|
||||
LogHelper.info('Starting the TCP server...')
|
||||
|
||||
const tcpServerCommand = `${TCP_SERVER_BIN_PATH} en`
|
||||
const tcpServerStart = Date.now()
|
||||
const p = spawn(tcpServerCommand, { shell: true })
|
||||
const ignoredWarnings = [
|
||||
'UserWarning: Unable to retrieve source for @torch.jit._overload function'
|
||||
]
|
||||
|
||||
LogHelper.info(tcpServerCommand)
|
||||
pastebinData.tcpServer.command = tcpServerCommand
|
||||
reportDataInput.tcpServer.command = tcpServerCommand
|
||||
|
||||
if (osInfo.platform === 'darwin') {
|
||||
LogHelper.info(
|
||||
@@ -240,10 +324,14 @@ dotenv.config()
|
||||
|
||||
p.stderr.on('data', (data) => {
|
||||
const newData = data.toString()
|
||||
tcpServerOutput += newData
|
||||
report.can_start_tcp_server.v = false
|
||||
pastebinData.tcpServer.error = newData
|
||||
LogHelper.error(`Cannot start the TCP server: ${newData}`)
|
||||
|
||||
// Ignore given warnings on stderr output
|
||||
if (!ignoredWarnings.some((w) => newData.includes(w))) {
|
||||
tcpServerOutput += newData
|
||||
report.can_start_tcp_server.v = false
|
||||
reportDataInput.tcpServer.error = newData
|
||||
LogHelper.error(`Cannot start the TCP server: ${newData}`)
|
||||
}
|
||||
})
|
||||
|
||||
const timeout = 3 * 60_000
|
||||
@@ -253,16 +341,16 @@ dotenv.config()
|
||||
|
||||
const error = `The TCP server timed out after ${timeout}ms`
|
||||
LogHelper.error(error)
|
||||
pastebinData.tcpServer.error = error
|
||||
reportDataInput.tcpServer.error = error
|
||||
report.can_start_tcp_server.v = false
|
||||
}, timeout)
|
||||
|
||||
p.stdout.on('end', async () => {
|
||||
const tcpServerEnd = Date.now()
|
||||
pastebinData.tcpServer.output = tcpServerOutput
|
||||
pastebinData.tcpServer.startTime = `${tcpServerEnd - tcpServerStart}ms`
|
||||
reportDataInput.tcpServer.output = tcpServerOutput
|
||||
reportDataInput.tcpServer.startTime = `${tcpServerEnd - tcpServerStart}ms`
|
||||
LogHelper.info(
|
||||
`TCP server startup time: ${pastebinData.tcpServer.startTime}ms\n`
|
||||
`TCP server startup time: ${reportDataInput.tcpServer.startTime}\n`
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -273,7 +361,8 @@ dotenv.config()
|
||||
|
||||
if (
|
||||
!fs.existsSync(globalResolversNlpModelPath) ||
|
||||
!Object.keys(fs.readFileSync(globalResolversNlpModelPath)).length
|
||||
!Object.keys(await fs.promises.readFile(globalResolversNlpModelPath))
|
||||
.length
|
||||
) {
|
||||
const state = 'Global resolvers NLP model not found or broken'
|
||||
|
||||
@@ -285,12 +374,12 @@ dotenv.config()
|
||||
LogHelper.error(
|
||||
`${state}. Try to generate a new one: "npm run train"\n`
|
||||
)
|
||||
pastebinData.nlpModels.globalResolversModelState = state
|
||||
reportDataInput.nlpModels.globalResolversModelState = state
|
||||
} else {
|
||||
const state = 'Found and valid'
|
||||
|
||||
LogHelper.success(`${state}\n`)
|
||||
pastebinData.nlpModels.globalResolversModelState = state
|
||||
reportDataInput.nlpModels.globalResolversModelState = state
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -301,7 +390,8 @@ dotenv.config()
|
||||
|
||||
if (
|
||||
!fs.existsSync(skillsResolversNlpModelPath) ||
|
||||
!Object.keys(fs.readFileSync(skillsResolversNlpModelPath)).length
|
||||
!Object.keys(await fs.promises.readFile(skillsResolversNlpModelPath))
|
||||
.length
|
||||
) {
|
||||
const state = 'Skills resolvers NLP model not found or broken'
|
||||
|
||||
@@ -313,12 +403,12 @@ dotenv.config()
|
||||
LogHelper.error(
|
||||
`${state}. Try to generate a new one: "npm run train"\n`
|
||||
)
|
||||
pastebinData.nlpModels.skillsResolversModelState = state
|
||||
reportDataInput.nlpModels.skillsResolversModelState = state
|
||||
} else {
|
||||
const state = 'Found and valid'
|
||||
|
||||
LogHelper.success(`${state}\n`)
|
||||
pastebinData.nlpModels.skillsResolversModelState = state
|
||||
reportDataInput.nlpModels.skillsResolversModelState = state
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -329,7 +419,7 @@ dotenv.config()
|
||||
|
||||
if (
|
||||
!fs.existsSync(mainNlpModelPath) ||
|
||||
!Object.keys(fs.readFileSync(mainNlpModelPath)).length
|
||||
!Object.keys(await fs.promises.readFile(mainNlpModelPath)).length
|
||||
) {
|
||||
const state = 'Main NLP model not found or broken'
|
||||
|
||||
@@ -341,12 +431,12 @@ dotenv.config()
|
||||
LogHelper.error(
|
||||
`${state}. Try to generate a new one: "npm run train"\n`
|
||||
)
|
||||
pastebinData.nlpModels.mainModelState = state
|
||||
reportDataInput.nlpModels.mainModelState = state
|
||||
} else {
|
||||
const state = 'Found and valid'
|
||||
|
||||
LogHelper.success(`${state}\n`)
|
||||
pastebinData.nlpModels.mainModelState = state
|
||||
reportDataInput.nlpModels.mainModelState = state
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -356,7 +446,7 @@ dotenv.config()
|
||||
LogHelper.info('Amazon Polly TTS')
|
||||
|
||||
try {
|
||||
const json = JSON.parse(fs.readFileSync(amazonPath))
|
||||
const json = JSON.parse(await fs.promises.readFile(amazonPath))
|
||||
if (
|
||||
json.credentials.accessKeyId === '' ||
|
||||
json.credentials.secretAccessKey === ''
|
||||
@@ -374,7 +464,7 @@ dotenv.config()
|
||||
LogHelper.info('Google Cloud TTS/STT')
|
||||
|
||||
try {
|
||||
const json = JSON.parse(fs.readFileSync(googleCloudPath))
|
||||
const json = JSON.parse(await fs.promises.readFile(googleCloudPath))
|
||||
const results = []
|
||||
Object.keys(json).forEach((item) => {
|
||||
if (json[item] === '') results.push(false)
|
||||
@@ -395,7 +485,7 @@ dotenv.config()
|
||||
LogHelper.info('Watson TTS')
|
||||
|
||||
try {
|
||||
const json = JSON.parse(fs.readFileSync(watsonTtsPath))
|
||||
const json = JSON.parse(await fs.promises.readFile(watsonTtsPath))
|
||||
const results = []
|
||||
Object.keys(json).forEach((item) => {
|
||||
if (json[item] === '') results.push(false)
|
||||
@@ -416,7 +506,7 @@ dotenv.config()
|
||||
if (!fs.existsSync(flitePath)) {
|
||||
report.can_offline_tts.v = false
|
||||
LogHelper.warning(
|
||||
`Cannot find ${flitePath}. You can setup the offline TTS by running: "npm run setup:offline-tts"\n`
|
||||
`Cannot find ${flitePath}. You can set up the offline TTS by running: "npm run setup:offline-tts"\n`
|
||||
)
|
||||
} else {
|
||||
LogHelper.success(`Found Flite at ${flitePath}\n`)
|
||||
@@ -425,7 +515,7 @@ dotenv.config()
|
||||
LogHelper.info('Watson STT')
|
||||
|
||||
try {
|
||||
const json = JSON.parse(fs.readFileSync(watsonSttPath))
|
||||
const json = JSON.parse(await fs.promises.readFile(watsonSttPath))
|
||||
const results = []
|
||||
Object.keys(json).forEach((item) => {
|
||||
if (json[item] === '') results.push(false)
|
||||
@@ -484,21 +574,23 @@ dotenv.config()
|
||||
LogHelper.error('Please fix the errors above')
|
||||
}
|
||||
|
||||
pastebinData.report = report
|
||||
reportDataInput.report = report
|
||||
|
||||
reportDataInput = JSON.parse(
|
||||
SystemHelper.sanitizeUsername(JSON.stringify(reportDataInput))
|
||||
)
|
||||
|
||||
LogHelper.title('REPORT URL')
|
||||
|
||||
LogHelper.info('Sending report...')
|
||||
|
||||
try {
|
||||
const { body } = await request
|
||||
.post('https://getleon.ai/api/report')
|
||||
.send({
|
||||
report: pastebinData
|
||||
})
|
||||
const { data: reportData } = body
|
||||
const { data } = await axios.post('https://getleon.ai/api/report', {
|
||||
report: reportDataInput
|
||||
})
|
||||
const { data: responseReportData } = data
|
||||
|
||||
LogHelper.success(`Report URL: ${reportData.reportUrl}`)
|
||||
LogHelper.success(`Report URL: ${responseReportData.reportUrl}`)
|
||||
} catch (e) {
|
||||
LogHelper.error(`Failed to send report: ${e}`)
|
||||
}
|
||||
|
||||
@@ -22,13 +22,13 @@ export default () =>
|
||||
try {
|
||||
// TODO: handle case where the memory folder contain multiple DB nodes
|
||||
const dbFolder = join(currentSkill.path, 'memory')
|
||||
const dbTestFiles = fs
|
||||
.readdirSync(dbFolder)
|
||||
.filter((entity) => entity.indexOf('.spec.json') !== -1)
|
||||
const dbTestFiles = (await fs.promises.readdir(dbFolder)).filter(
|
||||
(entity) => entity.indexOf('.spec.json') !== -1
|
||||
)
|
||||
|
||||
if (dbTestFiles.length > 0) {
|
||||
LogHelper.info(`Deleting ${dbTestFiles[0]}...`)
|
||||
fs.unlinkSync(join(dbFolder, dbTestFiles[0]))
|
||||
await fs.promises.unlink(join(dbFolder, dbTestFiles[0]))
|
||||
LogHelper.success(`${dbTestFiles[0]} deleted`)
|
||||
}
|
||||
} catch (e) {
|
||||
|
||||
+20
-15
@@ -6,24 +6,29 @@ import { LogHelper } from '@/helpers/log-helper'
|
||||
* This script is executed after "git commit" or "git merge" (Git hook https://git-scm.com/docs/githooks#_commit_msg)
|
||||
* it ensures the authenticity of commit messages
|
||||
*/
|
||||
LogHelper.info('Checking commit message...')
|
||||
;(async () => {
|
||||
LogHelper.info('Checking commit message...')
|
||||
|
||||
const commitEditMsgFile = '.git/COMMIT_EDITMSG'
|
||||
const commitEditMsgFile = '.git/COMMIT_EDITMSG'
|
||||
|
||||
if (fs.existsSync(commitEditMsgFile)) {
|
||||
try {
|
||||
const commitMessage = fs.readFileSync(commitEditMsgFile, 'utf8')
|
||||
const regex =
|
||||
'(build|BREAKING|chore|ci|docs|feat|fix|perf|refactor|style|test)(\\((web app|docker|server|hotword|tcp server|python bridge|skill\\/([\\w-]+)))?\\)?: .{1,50}'
|
||||
if (fs.existsSync(commitEditMsgFile)) {
|
||||
try {
|
||||
const commitMessage = await fs.promises.readFile(
|
||||
commitEditMsgFile,
|
||||
'utf8'
|
||||
)
|
||||
const regex =
|
||||
'(build|BREAKING|chore|ci|docs|feat|fix|perf|refactor|style|test)(\\((web app|scripts|docker|server|hotword|tcp server|bridge\\/(python|nodejs)|skill\\/([\\w-]+)))?\\)?: .{1,50}'
|
||||
|
||||
if (commitMessage.match(regex) !== null) {
|
||||
LogHelper.success('Commit message validated')
|
||||
} else {
|
||||
LogHelper.error(`Commit message does not match the format: ${regex}`)
|
||||
if (commitMessage.match(regex) !== null) {
|
||||
LogHelper.success('Commit message validated')
|
||||
} else {
|
||||
LogHelper.error(`Commit message does not match the format: ${regex}`)
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (e) {
|
||||
LogHelper.error(e.message)
|
||||
process.exit(1)
|
||||
}
|
||||
} catch (e) {
|
||||
LogHelper.error(e.message)
|
||||
process.exit(1)
|
||||
}
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -14,7 +14,7 @@ dotenv.config()
|
||||
* Generate HTTP API key script
|
||||
* save it in the .env file
|
||||
*/
|
||||
const generateHttpApiKey = () =>
|
||||
const generateHTTPAPIKey = () =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
LogHelper.info('Generating the HTTP API key...')
|
||||
|
||||
@@ -23,7 +23,7 @@ const generateHttpApiKey = () =>
|
||||
const str = StringHelper.random(11)
|
||||
const dotEnvPath = path.join(process.cwd(), '.env')
|
||||
const envVarKey = 'LEON_HTTP_API_KEY'
|
||||
let content = fs.readFileSync(dotEnvPath, 'utf8')
|
||||
let content = await fs.promises.readFile(dotEnvPath, 'utf8')
|
||||
|
||||
shasum.update(str)
|
||||
const sha1 = shasum.digest('hex')
|
||||
@@ -39,7 +39,7 @@ const generateHttpApiKey = () =>
|
||||
|
||||
content = lines.join('\n')
|
||||
|
||||
fs.writeFileSync(dotEnvPath, content)
|
||||
await fs.promises.writeFile(dotEnvPath, content)
|
||||
LogHelper.success('HTTP API key generated')
|
||||
|
||||
resolve()
|
||||
@@ -56,17 +56,17 @@ export default () =>
|
||||
!process.env.LEON_HTTP_API_KEY ||
|
||||
process.env.LEON_HTTP_API_KEY === ''
|
||||
) {
|
||||
await generateHttpApiKey()
|
||||
await generateHTTPAPIKey()
|
||||
} else if (!process.env.IS_DOCKER) {
|
||||
const answer = await prompt({
|
||||
type: 'confirm',
|
||||
name: 'generate.httpApiKey',
|
||||
name: 'generate.httpAPIKey',
|
||||
message: 'Do you want to regenerate the HTTP API key?',
|
||||
default: false
|
||||
})
|
||||
|
||||
if (answer.generate.httpApiKey === true) {
|
||||
await generateHttpApiKey()
|
||||
if (answer.generate.httpAPIKey === true) {
|
||||
await generateHTTPAPIKey()
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,80 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
import {
|
||||
domainSchemaObject,
|
||||
skillSchemaObject,
|
||||
skillConfigSchemaObject
|
||||
} from '@/schemas/skill-schemas'
|
||||
import {
|
||||
globalEntitySchemaObject,
|
||||
globalResolverSchemaObject,
|
||||
globalAnswersSchemaObject
|
||||
} from '@/schemas/global-data-schemas'
|
||||
import {
|
||||
amazonVoiceConfiguration,
|
||||
googleCloudVoiceConfiguration,
|
||||
watsonVoiceConfiguration
|
||||
} from '@/schemas/voice-config-schemas'
|
||||
|
||||
/**
|
||||
* Generate JSON schemas
|
||||
* @param {string} categoryName
|
||||
* @param {Map<string, Object>} schemas
|
||||
*/
|
||||
export const generateSchemas = async (categoryName, schemas) => {
|
||||
const categorySchemasPath = path.join(process.cwd(), 'schemas', categoryName)
|
||||
|
||||
await fs.promises.mkdir(categorySchemasPath, { recursive: true })
|
||||
|
||||
for (const [schemaName, schemaObject] of schemas.entries()) {
|
||||
const schemaPath = path.join(categorySchemasPath, `${schemaName}.json`)
|
||||
|
||||
await fs.promises.writeFile(
|
||||
schemaPath,
|
||||
JSON.stringify(
|
||||
{
|
||||
$schema: 'https://json-schema.org/draft-07/schema',
|
||||
...schemaObject
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
export default async () => {
|
||||
LogHelper.info('Generating the JSON schemas...')
|
||||
|
||||
await Promise.all([
|
||||
generateSchemas(
|
||||
'global-data',
|
||||
new Map([
|
||||
['global-entity', globalEntitySchemaObject],
|
||||
['global-resolver', globalResolverSchemaObject],
|
||||
['global-answers', globalAnswersSchemaObject]
|
||||
])
|
||||
),
|
||||
generateSchemas(
|
||||
'skill-schemas',
|
||||
new Map([
|
||||
['domain', domainSchemaObject],
|
||||
['skill', skillSchemaObject],
|
||||
['skill-config', skillConfigSchemaObject]
|
||||
])
|
||||
),
|
||||
generateSchemas(
|
||||
'voice-config-schemas',
|
||||
new Map([
|
||||
['amazon', amazonVoiceConfiguration],
|
||||
['google-cloud', googleCloudVoiceConfiguration],
|
||||
['watson-stt', watsonVoiceConfiguration],
|
||||
['watson-tts', watsonVoiceConfiguration]
|
||||
])
|
||||
)
|
||||
])
|
||||
|
||||
LogHelper.success('JSON schemas generated')
|
||||
}
|
||||
@@ -39,7 +39,9 @@ export default () =>
|
||||
|
||||
// Check if a new routing generation is necessary
|
||||
if (fs.existsSync(outputFilePath)) {
|
||||
const mtimeEndpoints = fs.statSync(outputFilePath).mtime.getTime()
|
||||
const mtimeEndpoints = (
|
||||
await fs.promises.stat(outputFilePath)
|
||||
).mtime.getTime()
|
||||
|
||||
let i = 0
|
||||
for (const currentDomain of skillDomains.values()) {
|
||||
@@ -49,7 +51,7 @@ export default () =>
|
||||
for (let j = 0; j < skillKeys.length; j += 1) {
|
||||
const skillFriendlyName = skillKeys[j]
|
||||
const currentSkill = currentDomain.skills[skillFriendlyName]
|
||||
const fileInfo = fs.statSync(
|
||||
const fileInfo = await fs.promises.stat(
|
||||
path.join(currentSkill.path, 'config', `${lang}.json`)
|
||||
)
|
||||
const mtime = fileInfo.mtime.getTime()
|
||||
@@ -91,7 +93,7 @@ export default () =>
|
||||
`${lang}.json`
|
||||
)
|
||||
const { actions } = JSON.parse(
|
||||
fs.readFileSync(configFilePath, 'utf8')
|
||||
await fs.promises.readFile(configFilePath, 'utf8')
|
||||
)
|
||||
const actionsKeys = Object.keys(actions)
|
||||
|
||||
@@ -145,7 +147,10 @@ export default () =>
|
||||
|
||||
LogHelper.info(`Writing ${outputFile} file...`)
|
||||
try {
|
||||
fs.writeFileSync(outputFilePath, JSON.stringify(finalObj, null, 2))
|
||||
await fs.promises.writeFile(
|
||||
outputFilePath,
|
||||
JSON.stringify(finalObj, null, 2)
|
||||
)
|
||||
LogHelper.success(`${outputFile} file generated`)
|
||||
resolve()
|
||||
} catch (e) {
|
||||
|
||||
@@ -0,0 +1,14 @@
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
|
||||
import generateJsonSchemas from './generate-json-schemas'
|
||||
|
||||
/**
|
||||
* Execute the generating JSON schemas script
|
||||
*/
|
||||
;(async () => {
|
||||
try {
|
||||
await generateJsonSchemas()
|
||||
} catch (error) {
|
||||
LogHelper.error(`Failed to generate the json schemas: ${error}`)
|
||||
}
|
||||
})()
|
||||
@@ -35,9 +35,12 @@ export default (version) =>
|
||||
|
||||
const repoUrl = sh.stdout.substr(0, sh.stdout.lastIndexOf('.git'))
|
||||
const previousTag = sh.stdout.substr(sh.stdout.indexOf('\n') + 1).trim()
|
||||
const changelogData = fs.readFileSync(changelog, 'utf8')
|
||||
const changelogData = await fs.promises.readFile(changelog, 'utf8')
|
||||
const compareUrl = `${repoUrl}/compare/${previousTag}...v${version}`
|
||||
let tmpData = fs.readFileSync(`scripts/tmp/${tmpChangelog}`, 'utf8')
|
||||
let tmpData = await fs.promises.readFile(
|
||||
`scripts/tmp/${tmpChangelog}`,
|
||||
'utf8'
|
||||
)
|
||||
|
||||
LogHelper.success(`Remote origin URL gotten: ${repoUrl}.git`)
|
||||
LogHelper.success(`Previous tag gotten: ${previousTag}`)
|
||||
@@ -46,14 +49,14 @@ export default (version) =>
|
||||
tmpData = tmpData.replace(version, `[${version}](${compareUrl})`)
|
||||
}
|
||||
|
||||
fs.writeFile(changelog, `${tmpData}${changelogData}`, (err) => {
|
||||
if (err) LogHelper.error(`Failed to write into file: ${err}`)
|
||||
else {
|
||||
fs.unlinkSync(`scripts/tmp/${tmpChangelog}`)
|
||||
LogHelper.success(`${changelog} generated`)
|
||||
resolve()
|
||||
}
|
||||
})
|
||||
try {
|
||||
await fs.promises.writeFile(changelog, `${tmpData}${changelogData}`)
|
||||
await fs.promises.unlink(`scripts/tmp/${tmpChangelog}`)
|
||||
LogHelper.success(`${changelog} generated`)
|
||||
resolve()
|
||||
} catch (error) {
|
||||
LogHelper.error(`Failed to write into file: ${error}`)
|
||||
}
|
||||
} catch (e) {
|
||||
LogHelper.error(`Error during git commands: ${e}`)
|
||||
reject(e)
|
||||
|
||||
@@ -3,7 +3,11 @@ import path from 'node:path'
|
||||
import { prompt } from 'inquirer'
|
||||
import { command } from 'execa'
|
||||
|
||||
import { PYTHON_BRIDGE_SRC_PATH, TCP_SERVER_SRC_PATH } from '@/constants'
|
||||
import {
|
||||
NODEJS_BRIDGE_SRC_PATH,
|
||||
PYTHON_BRIDGE_SRC_PATH,
|
||||
TCP_SERVER_SRC_PATH
|
||||
} from '@/constants'
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
import { LoaderHelper } from '@/helpers/loader-helper'
|
||||
|
||||
@@ -15,13 +19,17 @@ import { LoaderHelper } from '@/helpers/loader-helper'
|
||||
|
||||
const BUILD_TARGETS = new Map()
|
||||
|
||||
BUILD_TARGETS.set('nodejs-bridge', {
|
||||
workflowFileName: 'pre-release-nodejs-bridge.yml',
|
||||
versionFilePath: path.join(NODEJS_BRIDGE_SRC_PATH, 'version.ts')
|
||||
})
|
||||
BUILD_TARGETS.set('python-bridge', {
|
||||
workflowFileName: 'pre-release-python-bridge.yml',
|
||||
setupFilePath: path.join(PYTHON_BRIDGE_SRC_PATH, 'setup.py')
|
||||
versionFilePath: path.join(PYTHON_BRIDGE_SRC_PATH, 'version.py')
|
||||
})
|
||||
BUILD_TARGETS.set('tcp-server', {
|
||||
workflowFileName: 'pre-release-tcp-server.yml',
|
||||
setupFilePath: path.join(TCP_SERVER_SRC_PATH, 'setup.py')
|
||||
versionFilePath: path.join(TCP_SERVER_SRC_PATH, 'version.py')
|
||||
})
|
||||
;(async () => {
|
||||
LoaderHelper.start()
|
||||
@@ -29,21 +37,21 @@ BUILD_TARGETS.set('tcp-server', {
|
||||
const { argv } = process
|
||||
const givenReleaseTarget = argv[2].toLowerCase()
|
||||
const givenBranch = argv[3]?.toLowerCase()
|
||||
const { workflowFileName, setupFilePath } =
|
||||
const { workflowFileName, versionFilePath } =
|
||||
BUILD_TARGETS.get(givenReleaseTarget)
|
||||
|
||||
LoaderHelper.stop()
|
||||
const answer = await prompt({
|
||||
type: 'confirm',
|
||||
name: 'binary.bumped',
|
||||
message: `Have you bumped the version number of the binary from the "${setupFilePath}" file?`,
|
||||
message: `Have you bumped the version number of the binary from the "${versionFilePath}" file?`,
|
||||
default: false
|
||||
})
|
||||
LoaderHelper.start()
|
||||
|
||||
if (!answer.binary.bumped) {
|
||||
LogHelper.info(
|
||||
'Please bump the version number of the binary from the setup file before continuing'
|
||||
'Please bump the version number of the binary from the version file before continuing'
|
||||
)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
@@ -10,7 +10,8 @@ export default (version) =>
|
||||
LogHelper.info('Updating version...')
|
||||
|
||||
const promises = []
|
||||
const files = ['package.json', 'package-lock.json']
|
||||
// const files = ['package.json', 'package-lock.json']
|
||||
const files = ['package.json']
|
||||
|
||||
for (let i = 0; i < files.length; i += 1) {
|
||||
promises.push(
|
||||
|
||||
@@ -9,6 +9,6 @@ import setupHotword from './setup-hotword'
|
||||
try {
|
||||
await setupHotword()
|
||||
} catch (e) {
|
||||
LogHelper.error(`Failed to setup offline hotword: ${e}`)
|
||||
LogHelper.error(`Failed to set up offline hotword: ${e}`)
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -9,6 +9,6 @@ import setupStt from './setup-stt'
|
||||
try {
|
||||
await setupStt()
|
||||
} catch (e) {
|
||||
LogHelper.error(`Failed to setup offline STT: ${e}`)
|
||||
LogHelper.error(`Failed to set up offline STT: ${e}`)
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -9,6 +9,6 @@ import setupTts from './setup-tts'
|
||||
try {
|
||||
await setupTts()
|
||||
} catch (e) {
|
||||
LogHelper.error(`Failed to setup offline TTS: ${e}`)
|
||||
LogHelper.error(`Failed to set up offline TTS: ${e}`)
|
||||
}
|
||||
})()
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import { command } from 'execa'
|
||||
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
import { OSHelper } from '@/helpers/os-helper'
|
||||
import { SystemHelper } from '@/helpers/system-helper'
|
||||
|
||||
/**
|
||||
* Setup offline hotword detection
|
||||
@@ -10,7 +10,7 @@ export default () =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
LogHelper.info('Setting up offline hotword detection...')
|
||||
|
||||
const info = OSHelper.getInformation()
|
||||
const info = SystemHelper.getInformation()
|
||||
let pkgm = 'apt-get install'
|
||||
if (info.type === 'macos') {
|
||||
pkgm = 'brew'
|
||||
|
||||
@@ -3,10 +3,10 @@ import fs from 'node:fs'
|
||||
import { command } from 'execa'
|
||||
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
import { OSHelper } from '@/helpers/os-helper'
|
||||
import { SystemHelper } from '@/helpers/system-helper'
|
||||
|
||||
/**
|
||||
* Setup offline speech-to-text
|
||||
* Set up offline speech-to-text
|
||||
*/
|
||||
export default () =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
@@ -17,7 +17,7 @@ export default () =>
|
||||
// check this repo for updates: https://github.com/coqui-ai/STT-models/tree/main/english/coqui
|
||||
const coquiModelVersion = '1.0.0'
|
||||
let downloader = 'wget'
|
||||
if (OSHelper.getInformation().type === 'macos') {
|
||||
if (SystemHelper.getInformation().type === 'macos') {
|
||||
downloader = 'curl -L -O'
|
||||
}
|
||||
|
||||
|
||||
@@ -3,10 +3,10 @@ import fs from 'node:fs'
|
||||
import { command } from 'execa'
|
||||
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
import { OSHelper } from '@/helpers/os-helper'
|
||||
import { SystemHelper } from '@/helpers/system-helper'
|
||||
|
||||
/**
|
||||
* Setup offline text-to-speech
|
||||
* Set up offline text-to-speech
|
||||
*/
|
||||
export default () =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
@@ -15,11 +15,11 @@ export default () =>
|
||||
const destFliteFolder = 'bin/flite'
|
||||
const tmpDir = 'scripts/tmp'
|
||||
let makeCores = ''
|
||||
if (OSHelper.getNumberOfCPUCores() > 2) {
|
||||
makeCores = `-j ${OSHelper.getNumberOfCPUCores() - 2}`
|
||||
if (SystemHelper.getNumberOfCPUCores() > 2) {
|
||||
makeCores = `-j ${SystemHelper.getNumberOfCPUCores() - 2}`
|
||||
}
|
||||
let downloader = 'wget'
|
||||
if (OSHelper.getInformation().type === 'macos') {
|
||||
if (SystemHelper.getInformation().type === 'macos') {
|
||||
downloader = 'curl -L -O'
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
import fs from 'node:fs'
|
||||
|
||||
import { LEON_FILE_PATH } from '@/constants'
|
||||
import { Telemetry } from '@/telemetry'
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
|
||||
export default async () => {
|
||||
try {
|
||||
const { instanceID, birthDate } = await Telemetry.postInstall()
|
||||
|
||||
if (!fs.existsSync(LEON_FILE_PATH)) {
|
||||
await fs.promises.writeFile(
|
||||
LEON_FILE_PATH,
|
||||
JSON.stringify(
|
||||
{
|
||||
instanceID,
|
||||
birthDate
|
||||
},
|
||||
null,
|
||||
2
|
||||
)
|
||||
)
|
||||
|
||||
LogHelper.success(`Instance ID created: ${instanceID}`)
|
||||
} else {
|
||||
LogHelper.success(`Instance ID already exists: ${instanceID}`)
|
||||
}
|
||||
} catch (e) {
|
||||
LogHelper.warning(`Failed to create the instance ID: ${e}`)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,176 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import stream from 'node:stream'
|
||||
import readline from 'node:readline'
|
||||
|
||||
import axios from 'axios'
|
||||
import { command } from 'execa'
|
||||
import prettyBytes from 'pretty-bytes'
|
||||
import prettyMilliseconds from 'pretty-ms'
|
||||
import extractZip from 'extract-zip'
|
||||
|
||||
import {
|
||||
BINARIES_FOLDER_NAME,
|
||||
GITHUB_URL,
|
||||
NODEJS_BRIDGE_ROOT_PATH,
|
||||
NODEJS_BRIDGE_DIST_PATH,
|
||||
PYTHON_BRIDGE_DIST_PATH,
|
||||
TCP_SERVER_DIST_PATH,
|
||||
NODEJS_BRIDGE_BIN_NAME,
|
||||
PYTHON_BRIDGE_BIN_NAME,
|
||||
TCP_SERVER_BIN_NAME,
|
||||
NODEJS_BRIDGE_VERSION,
|
||||
PYTHON_BRIDGE_VERSION,
|
||||
TCP_SERVER_VERSION
|
||||
} from '@/constants'
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
|
||||
/**
|
||||
* Set up binaries according to the given setup target
|
||||
* 1. Delete the existing dist binaries if already exist
|
||||
* 2. Download the latest binaries from GitHub releases
|
||||
* 3. Extract the downloaded ZIP file to the dist folder
|
||||
*/
|
||||
|
||||
const TARGETS = new Map()
|
||||
|
||||
TARGETS.set('nodejs-bridge', {
|
||||
name: 'Node.js bridge',
|
||||
distPath: NODEJS_BRIDGE_DIST_PATH,
|
||||
manifestPath: path.join(NODEJS_BRIDGE_DIST_PATH, 'manifest.json'),
|
||||
archiveName: `${NODEJS_BRIDGE_BIN_NAME.split('.')[0]}.zip`,
|
||||
version: NODEJS_BRIDGE_VERSION,
|
||||
isPlatformDependent: false // Need to be built for the target platform or not
|
||||
})
|
||||
TARGETS.set('python-bridge', {
|
||||
name: 'Python bridge',
|
||||
distPath: PYTHON_BRIDGE_DIST_PATH,
|
||||
manifestPath: path.join(PYTHON_BRIDGE_DIST_PATH, 'manifest.json'),
|
||||
archiveName: `${PYTHON_BRIDGE_BIN_NAME}-${BINARIES_FOLDER_NAME}.zip`,
|
||||
version: PYTHON_BRIDGE_VERSION,
|
||||
isPlatformDependent: true
|
||||
})
|
||||
TARGETS.set('tcp-server', {
|
||||
name: 'TCP server',
|
||||
distPath: TCP_SERVER_DIST_PATH,
|
||||
manifestPath: path.join(TCP_SERVER_DIST_PATH, 'manifest.json'),
|
||||
archiveName: `${TCP_SERVER_BIN_NAME}-${BINARIES_FOLDER_NAME}.zip`,
|
||||
version: TCP_SERVER_VERSION,
|
||||
isPlatformDependent: true
|
||||
})
|
||||
|
||||
async function createManifestFile(manifestPath, name, version) {
|
||||
const manifest = {
|
||||
name,
|
||||
version,
|
||||
setupDate: Date.now()
|
||||
}
|
||||
|
||||
await fs.promises.writeFile(manifestPath, JSON.stringify(manifest, null, 2))
|
||||
}
|
||||
|
||||
const setupBinaries = async (key) => {
|
||||
const {
|
||||
name,
|
||||
distPath,
|
||||
archiveName,
|
||||
version,
|
||||
manifestPath,
|
||||
isPlatformDependent
|
||||
} = TARGETS.get(key)
|
||||
let manifest = null
|
||||
|
||||
if (fs.existsSync(manifestPath)) {
|
||||
manifest = JSON.parse(await fs.promises.readFile(manifestPath, 'utf8'))
|
||||
|
||||
LogHelper.info(`Found ${name} ${manifest.version}`)
|
||||
LogHelper.info(`Latest version is ${version}`)
|
||||
}
|
||||
|
||||
if (!manifest || manifest.version !== version) {
|
||||
const buildPath = isPlatformDependent
|
||||
? path.join(distPath, BINARIES_FOLDER_NAME)
|
||||
: path.join(distPath, 'bin')
|
||||
const archivePath = path.join(distPath, archiveName)
|
||||
|
||||
await Promise.all([
|
||||
fs.promises.rm(buildPath, { recursive: true, force: true }),
|
||||
fs.promises.rm(archivePath, { recursive: true, force: true })
|
||||
])
|
||||
|
||||
if (key === 'nodejs-bridge') {
|
||||
try {
|
||||
LogHelper.info('Installing Node.js bridge npm packages...')
|
||||
|
||||
await command(
|
||||
`npm install --package-lock=false --prefix ${NODEJS_BRIDGE_ROOT_PATH}`,
|
||||
{
|
||||
shell: true
|
||||
}
|
||||
)
|
||||
|
||||
LogHelper.success('Node.js bridge npm packages installed')
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to install Node.js bridge npm packages: ${e}`)
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
LogHelper.info(`Downloading ${name}...`)
|
||||
|
||||
const archiveWriter = fs.createWriteStream(archivePath)
|
||||
const latestReleaseAssetURL = `${GITHUB_URL}/releases/download/${key}_v${version}/${archiveName}`
|
||||
const { data } = await axios.get(latestReleaseAssetURL, {
|
||||
responseType: 'stream',
|
||||
onDownloadProgress: ({ loaded, total, progress, estimated, rate }) => {
|
||||
const percentage = Math.floor(progress * 100)
|
||||
const downloadedSize = prettyBytes(loaded)
|
||||
const totalSize = prettyBytes(total)
|
||||
const estimatedTime = !estimated
|
||||
? 0
|
||||
: prettyMilliseconds(estimated * 1_000, { secondsDecimalDigits: 0 })
|
||||
const downloadRate = !rate ? 0 : prettyBytes(rate)
|
||||
|
||||
readline.clearLine(process.stdout, 0)
|
||||
readline.cursorTo(process.stdout, 0, null)
|
||||
process.stdout.write(
|
||||
`Download progress: ${percentage}% (${downloadedSize}/${totalSize} | ${downloadRate}/s | ${estimatedTime} ETA)`
|
||||
)
|
||||
|
||||
if (percentage === 100) {
|
||||
process.stdout.write('\n')
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
data.pipe(archiveWriter)
|
||||
await stream.promises.finished(archiveWriter)
|
||||
|
||||
LogHelper.success(`${name} downloaded`)
|
||||
LogHelper.info(`Extracting ${name}...`)
|
||||
|
||||
const absoluteDistPath = path.resolve(distPath)
|
||||
await extractZip(archivePath, { dir: absoluteDistPath })
|
||||
|
||||
LogHelper.success(`${name} extracted`)
|
||||
|
||||
await Promise.all([
|
||||
fs.promises.rm(archivePath, { recursive: true, force: true }),
|
||||
createManifestFile(manifestPath, name, version)
|
||||
])
|
||||
|
||||
LogHelper.success(`${name} manifest file created`)
|
||||
LogHelper.success(`${name} ${version} ready`)
|
||||
} catch (e) {
|
||||
throw new Error(`Failed to set up ${name}: ${e}`)
|
||||
}
|
||||
} else {
|
||||
LogHelper.success(`${name} is already at the latest version (${version})`)
|
||||
}
|
||||
}
|
||||
|
||||
export default async () => {
|
||||
await setupBinaries('nodejs-bridge')
|
||||
await setupBinaries('python-bridge')
|
||||
await setupBinaries('tcp-server')
|
||||
}
|
||||
@@ -4,23 +4,23 @@ import path from 'node:path'
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
|
||||
/**
|
||||
* Setup Leon's core configuration
|
||||
* Set up Leon's core configuration
|
||||
*/
|
||||
export default () =>
|
||||
new Promise((resolve) => {
|
||||
new Promise(async (resolve) => {
|
||||
LogHelper.info('Configuring core...')
|
||||
|
||||
const dir = 'core/config'
|
||||
const list = (dir) => {
|
||||
const entities = fs.readdirSync(dir)
|
||||
const list = async (dir) => {
|
||||
const entities = await fs.promises.readdir(dir)
|
||||
|
||||
// Browse core config entities
|
||||
for (let i = 0; i < entities.length; i += 1) {
|
||||
const file = `${entities[i].replace('.sample.json', '.json')}`
|
||||
// Recursive if the entity is a directory
|
||||
const way = path.join(dir, entities[i])
|
||||
if (fs.statSync(way).isDirectory()) {
|
||||
list(way)
|
||||
if ((await fs.promises.stat(way)).isDirectory()) {
|
||||
await list(way)
|
||||
} else if (
|
||||
entities[i].indexOf('.sample.json') !== -1 &&
|
||||
!fs.existsSync(`${dir}/${file}`)
|
||||
@@ -40,6 +40,6 @@ export default () =>
|
||||
}
|
||||
}
|
||||
|
||||
list(dir)
|
||||
await list(dir)
|
||||
resolve()
|
||||
})
|
||||
|
||||
@@ -11,9 +11,10 @@ import {
|
||||
PYTHON_BRIDGE_SRC_PATH,
|
||||
TCP_SERVER_SRC_PATH
|
||||
} from '@/constants'
|
||||
import { CPUArchitectures, OSTypes } from '@/types'
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
import { LoaderHelper } from '@/helpers/loader-helper'
|
||||
import { CPUArchitectures, OSHelper, OSTypes } from '@/helpers/os-helper'
|
||||
import { SystemHelper } from '@/helpers/system-helper'
|
||||
|
||||
/**
|
||||
* Set up development environment according to the given setup target
|
||||
@@ -128,19 +129,19 @@ SPACY_MODELS.set('fr', {
|
||||
|
||||
const pipfileMtime = fs.statSync(pipfilePath).mtime
|
||||
const hasDotVenv = fs.existsSync(dotVenvPath)
|
||||
const { type: osType, cpuArchitecture } = OSHelper.getInformation()
|
||||
const { type: osType, cpuArchitecture } = SystemHelper.getInformation()
|
||||
const installPythonPackages = async () => {
|
||||
LogHelper.info(`Installing Python packages from ${pipfilePath}.lock...`)
|
||||
|
||||
// Delete .venv directory to reset the development environment
|
||||
if (hasDotVenv) {
|
||||
LogHelper.info(`Deleting ${dotVenvPath}...`)
|
||||
fs.rmSync(dotVenvPath, { recursive: true, force: true })
|
||||
await fs.promises.rm(dotVenvPath, { recursive: true, force: true })
|
||||
LogHelper.success(`${dotVenvPath} deleted`)
|
||||
}
|
||||
|
||||
try {
|
||||
await command(`pipenv install --verbose --site-packages`, {
|
||||
await command('pipenv install --verbose --site-packages', {
|
||||
shell: true,
|
||||
stdio: 'inherit'
|
||||
})
|
||||
@@ -154,21 +155,21 @@ SPACY_MODELS.set('fr', {
|
||||
LogHelper.info(
|
||||
'Installing Rust installer as it is needed for the "tokenizers" package for macOS ARM64 architecture...'
|
||||
)
|
||||
await command(`curl https://sh.rustup.rs -sSf | sh -s -- -y`, {
|
||||
await command('curl https://sh.rustup.rs -sSf | sh -s -- -y', {
|
||||
shell: true,
|
||||
stdio: 'inherit'
|
||||
})
|
||||
LogHelper.success('Rust installer installed')
|
||||
|
||||
LogHelper.info('Reloading configuration from "$HOME/.cargo/env"...')
|
||||
await command(`source "$HOME/.cargo/env"`, {
|
||||
await command('source "$HOME/.cargo/env"', {
|
||||
shell: true,
|
||||
stdio: 'inherit'
|
||||
})
|
||||
LogHelper.success('Configuration reloaded')
|
||||
|
||||
LogHelper.info('Checking Rust compiler version...')
|
||||
await command(`rustc --version`, {
|
||||
await command('rustc --version', {
|
||||
shell: true,
|
||||
stdio: 'inherit'
|
||||
})
|
||||
@@ -210,7 +211,7 @@ SPACY_MODELS.set('fr', {
|
||||
await installPythonPackages()
|
||||
} else {
|
||||
if (fs.existsSync(dotProjectPath)) {
|
||||
const dotProjectMtime = fs.statSync(dotProjectPath).mtime
|
||||
const dotProjectMtime = (await fs.promises.stat(dotProjectPath)).mtime
|
||||
|
||||
// Check if Python deps tree has been modified since the initial setup
|
||||
if (pipfileMtime > dotProjectMtime) {
|
||||
|
||||
@@ -1,102 +0,0 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
import { commandSync } from 'execa'
|
||||
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
import { SkillDomainHelper } from '@/helpers/skill-domain-helper'
|
||||
|
||||
/**
|
||||
* Setup skills configuration
|
||||
*/
|
||||
export default () =>
|
||||
new Promise(async (resolve, reject) => {
|
||||
LogHelper.info('Setting up skills configuration...')
|
||||
|
||||
const skillDomains = await SkillDomainHelper.getSkillDomains()
|
||||
|
||||
for (const currentDomain of skillDomains.values()) {
|
||||
const skillKeys = Object.keys(currentDomain.skills)
|
||||
|
||||
// Browse skills
|
||||
for (let j = 0; j < skillKeys.length; j += 1) {
|
||||
const skillFriendlyName = skillKeys[j]
|
||||
const currentSkill = currentDomain.skills[skillFriendlyName]
|
||||
const configDir = path.join(currentSkill.path, 'src')
|
||||
const configFile = path.join(configDir, 'config.json')
|
||||
const configSampleFile = path.join(configDir, 'config.sample.json')
|
||||
|
||||
// If there is a bridge set from the skill config
|
||||
if (currentSkill.bridge) {
|
||||
// Check if the config and config.sample file exist
|
||||
if (fs.existsSync(configFile) && fs.existsSync(configSampleFile)) {
|
||||
const config = JSON.parse(
|
||||
fs.readFileSync(configFile, 'utf8')
|
||||
)?.configurations
|
||||
const configSample = JSON.parse(
|
||||
fs.readFileSync(configSampleFile, 'utf8')
|
||||
)?.configurations
|
||||
const configKeys = Object.keys(config)
|
||||
const configSampleKeys = Object.keys(configSample)
|
||||
|
||||
// Check if there is a new config key in the config sample compared to the config.json
|
||||
if (
|
||||
JSON.stringify(configKeys) !== JSON.stringify(configSampleKeys)
|
||||
) {
|
||||
// Browse config keys of the new skill config
|
||||
for (let j = 0; j < configSampleKeys.length; j += 1) {
|
||||
// Check if the current config key does not exist
|
||||
if (configKeys.includes(configSampleKeys[j]) === false) {
|
||||
LogHelper.info(
|
||||
`Adding new configuration key "${configSampleKeys[j]}" for the ${skillFriendlyName} skill...`
|
||||
)
|
||||
|
||||
// Prepare to inject the new config key object
|
||||
const configKey = {
|
||||
[configSampleKeys[j]]: configSample[configSampleKeys[j]]
|
||||
}
|
||||
|
||||
try {
|
||||
// Add new skill configuration in the config.json file
|
||||
commandSync(
|
||||
`json -I -f ${configFile} -e 'this.configurations.${
|
||||
configSampleKeys[j]
|
||||
}=${JSON.stringify(configKey[configSampleKeys[j]])}'`,
|
||||
{ shell: true }
|
||||
)
|
||||
LogHelper.success(
|
||||
`"${configSampleKeys[j]}" configuration key added to ${configFile}`
|
||||
)
|
||||
} catch (e) {
|
||||
LogHelper.error(
|
||||
`Error while adding "${configSampleKeys[j]}" configuration key to ${configFile}: ${e}`
|
||||
)
|
||||
reject()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!fs.existsSync(configSampleFile)) {
|
||||
// Stop the setup if the config.sample.json of the current skill does not exist
|
||||
LogHelper.error(
|
||||
`The "${skillFriendlyName}" skill configuration file does not exist. Try to pull the project (git pull)`
|
||||
)
|
||||
reject()
|
||||
} else {
|
||||
// Duplicate config.sample.json of the current skill to config.json
|
||||
fs.createReadStream(configSampleFile).pipe(
|
||||
fs.createWriteStream(`${configDir}/config.json`)
|
||||
)
|
||||
|
||||
LogHelper.success(
|
||||
`"${skillFriendlyName}" skill configuration file created`
|
||||
)
|
||||
resolve()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.success('Skills configured')
|
||||
resolve()
|
||||
})
|
||||
@@ -0,0 +1,65 @@
|
||||
import path from 'node:path'
|
||||
import fs from 'node:fs'
|
||||
|
||||
import { command } from 'execa'
|
||||
|
||||
import { isFileEmpty } from '@/utilities'
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
|
||||
/**
|
||||
* Install Node.js skills packages on setup
|
||||
* 1. If skill is Node.js, then verify if an installation is needed
|
||||
* 2. If install is needed, then install npm packages
|
||||
*/
|
||||
export default async function (skillFriendlyName, currentSkill) {
|
||||
if (currentSkill.bridge === 'nodejs') {
|
||||
const skillSRCPath = path.join(currentSkill.path, 'src')
|
||||
const skillPackageJSONPath = path.join(skillSRCPath, 'package.json')
|
||||
|
||||
if (fs.existsSync(skillPackageJSONPath)) {
|
||||
const isPackageJSONEmpty = await isFileEmpty(skillPackageJSONPath)
|
||||
|
||||
if (!isPackageJSONEmpty) {
|
||||
const packageJSONMtime = (await fs.promises.stat(skillPackageJSONPath))
|
||||
.mtime
|
||||
const lastSkillNPMInstallFilePath = path.join(
|
||||
skillSRCPath,
|
||||
'.last-skill-npm-install'
|
||||
)
|
||||
|
||||
if (fs.existsSync(lastSkillNPMInstallFilePath)) {
|
||||
const lastSkillNPMInstallTime = new Date(
|
||||
Number(
|
||||
await fs.promises.readFile(lastSkillNPMInstallFilePath, 'utf8')
|
||||
)
|
||||
)
|
||||
|
||||
if (packageJSONMtime <= lastSkillNPMInstallTime) {
|
||||
LogHelper.success(
|
||||
`"${skillFriendlyName}" skill npm packages are up-to-date`
|
||||
)
|
||||
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.info(
|
||||
`Installing npm packages for the "${skillFriendlyName}" skill...`
|
||||
)
|
||||
|
||||
await command(
|
||||
`npm install --package-lock=false --prefix ${skillSRCPath}`,
|
||||
{
|
||||
shell: true
|
||||
}
|
||||
)
|
||||
await fs.promises.writeFile(
|
||||
lastSkillNPMInstallFilePath,
|
||||
`${Date.now()}`
|
||||
)
|
||||
|
||||
LogHelper.success(`"${skillFriendlyName}" skill npm packages installed`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
import fs from 'node:fs'
|
||||
import path from 'node:path'
|
||||
|
||||
import { commandSync } from 'execa'
|
||||
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
|
||||
/**
|
||||
* Set up skills settings
|
||||
*/
|
||||
export default async function (skillFriendlyName, currentSkill) {
|
||||
const skillSrcPath = path.join(currentSkill.path, 'src')
|
||||
const settingsPath = path.join(skillSrcPath, 'settings.json')
|
||||
const settingsSamplePath = path.join(skillSrcPath, 'settings.sample.json')
|
||||
|
||||
// If there is a bridge set from the skill settings
|
||||
if (currentSkill.bridge) {
|
||||
// Check if the settings and settings.sample file exist
|
||||
if (fs.existsSync(settingsPath) && fs.existsSync(settingsSamplePath)) {
|
||||
const settings = JSON.parse(
|
||||
await fs.promises.readFile(settingsPath, 'utf8')
|
||||
)
|
||||
const settingsSample = JSON.parse(
|
||||
await fs.promises.readFile(settingsSamplePath, 'utf8')
|
||||
)
|
||||
const settingsKeys = Object.keys(settings)
|
||||
const settingsSampleKeys = Object.keys(settingsSample)
|
||||
|
||||
// Check if there is a new settings key in the settings sample compared to the settings.json
|
||||
if (JSON.stringify(settingsKeys) !== JSON.stringify(settingsSampleKeys)) {
|
||||
// Browse settings keys of the new settings config
|
||||
for (let j = 0; j < settingsSampleKeys.length; j += 1) {
|
||||
// Check if the current settings key does not exist
|
||||
if (!settingsKeys.includes(settingsSampleKeys[j])) {
|
||||
LogHelper.info(
|
||||
`Adding new settings key "${settingsSampleKeys[j]}" for the ${skillFriendlyName} skill...`
|
||||
)
|
||||
|
||||
// Prepare to inject the new settings key object
|
||||
const configKey = {
|
||||
[settingsSampleKeys[j]]: settingsSample[settingsSampleKeys[j]]
|
||||
}
|
||||
|
||||
try {
|
||||
// Add new skill settings in the settings.json file
|
||||
commandSync(
|
||||
`json -I -f ${settingsPath} -e 'this.${
|
||||
settingsSampleKeys[j]
|
||||
}=${JSON.stringify(configKey[settingsSampleKeys[j]])}'`,
|
||||
{ shell: true }
|
||||
)
|
||||
LogHelper.success(
|
||||
`"${settingsSampleKeys[j]}" settings key added to ${settingsPath}`
|
||||
)
|
||||
} catch (e) {
|
||||
LogHelper.error(
|
||||
`Error while adding "${settingsSampleKeys[j]}" settings key to ${settingsPath}: ${e}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
} else if (!fs.existsSync(settingsSamplePath)) {
|
||||
// Stop the setup if the settings.sample.json of the current skill does not exist
|
||||
LogHelper.error(
|
||||
`The "${skillFriendlyName}" skill settings file does not exist. Try to pull the project (git pull)`
|
||||
)
|
||||
} else {
|
||||
// Duplicate settings.sample.json of the current skill to settings.json
|
||||
fs.createReadStream(settingsSamplePath).pipe(
|
||||
fs.createWriteStream(`${skillSrcPath}/settings.json`)
|
||||
)
|
||||
|
||||
LogHelper.success(`"${skillFriendlyName}" skill settings file created`)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,37 @@
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
import { SkillDomainHelper } from '@/helpers/skill-domain-helper'
|
||||
|
||||
import setupSkillsSettings from './setup-skills-settings'
|
||||
import installNodejsSkillsPackages from './install-nodejs-skills-packages'
|
||||
|
||||
/**
|
||||
* Browse skills and set them up
|
||||
*/
|
||||
export default async function () {
|
||||
LogHelper.info('Setting up skills...')
|
||||
|
||||
try {
|
||||
const skillDomains = await SkillDomainHelper.getSkillDomains()
|
||||
|
||||
for (const currentDomain of skillDomains.values()) {
|
||||
const skillKeys = Object.keys(currentDomain.skills)
|
||||
|
||||
// Browse skills
|
||||
for (let i = 0; i < skillKeys.length; i += 1) {
|
||||
const skillFriendlyName = skillKeys[i]
|
||||
const currentSkill = currentDomain.skills[skillFriendlyName]
|
||||
|
||||
LogHelper.info(`Setting up "${skillFriendlyName}" skill...`)
|
||||
|
||||
await setupSkillsSettings(skillFriendlyName, currentSkill)
|
||||
await installNodejsSkillsPackages(skillFriendlyName, currentSkill)
|
||||
|
||||
LogHelper.success(`"${skillFriendlyName}" skill set up`)
|
||||
}
|
||||
}
|
||||
|
||||
LogHelper.success('Skills are set up')
|
||||
} catch (e) {
|
||||
LogHelper.error(`Failed to set up skills: ${e}`)
|
||||
}
|
||||
}
|
||||
+11
-8
@@ -2,12 +2,14 @@ import { LoaderHelper } from '@/helpers/loader-helper'
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
|
||||
import train from '../train/train'
|
||||
import generateHttpApiKey from '../generate/generate-http-api-key'
|
||||
import generateHTTPAPIKey from '../generate/generate-http-api-key'
|
||||
import generateJSONSchemas from '../generate/generate-json-schemas'
|
||||
|
||||
import setupDotenv from './setup-dotenv'
|
||||
import setupCore from './setup-core'
|
||||
import setupSkillsConfig from './setup-skills-config'
|
||||
// import setupPythonPackages from './setup-python-packages'
|
||||
import setupSkills from './setup-skills/setup-skills'
|
||||
import setupBinaries from './setup-binaries'
|
||||
import createInstanceID from './create-instance-id'
|
||||
|
||||
// Do not load ".env" file because it is not created yet
|
||||
|
||||
@@ -18,14 +20,15 @@ import setupSkillsConfig from './setup-skills-config'
|
||||
try {
|
||||
await setupDotenv()
|
||||
LoaderHelper.start()
|
||||
await Promise.all([setupCore(), setupSkillsConfig()])
|
||||
// TODO: download Python bridge archive; unpack it; remove archive
|
||||
// TODO: download TCP server archive; unpack it; remove archive
|
||||
// await setupPythonPackages()
|
||||
await setupCore()
|
||||
await setupSkills()
|
||||
LoaderHelper.stop()
|
||||
await generateHttpApiKey()
|
||||
await setupBinaries()
|
||||
await generateHTTPAPIKey()
|
||||
await generateJSONSchemas()
|
||||
LoaderHelper.start()
|
||||
await train()
|
||||
await createInstanceID()
|
||||
|
||||
LogHelper.default('')
|
||||
LogHelper.success('Hooray! Leon is installed and ready to go!')
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user