Compare commits
395 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| bbb533f49f | |||
| bdff917e04 | |||
| 52cf4afbec | |||
| 0ef1ec9247 | |||
| aa862a6b5f | |||
| f10f310aef | |||
| 18b47f168a | |||
| f7ad05275b | |||
| d62e0cd56c | |||
| 1caa73a13a | |||
| 844af50008 | |||
| b26d924890 | |||
| da1b469f8d | |||
| 0189c74a0e | |||
| a0a4f9d7b0 | |||
| eeb1b07898 | |||
| 78e5f79bce | |||
| 764e815204 | |||
| 03d25f2a48 | |||
| aa36c8cb2a | |||
| 2146637022 | |||
| 0a4e0ed90d | |||
| ceeb5bd70e | |||
| f77343918d | |||
| e2984ea87e | |||
| 694d398d7b | |||
| a2bca433f6 | |||
| 60698704ca | |||
| 7d4e64d575 | |||
| 2c03efc167 | |||
| 539281110a | |||
| 3ed88544f8 | |||
| 9ee73ea5f9 | |||
| c934d0e30d | |||
| d9f0144e62 | |||
| 57923f83ee | |||
| a38eee71f0 | |||
| c8e03ec401 | |||
| 4ca32b5070 | |||
| d8660251af | |||
| 26af271edc | |||
| da49a4dd82 | |||
| cf02f0f91d | |||
| 2de95c4ef9 | |||
| 2434d36564 | |||
| 73d919868b | |||
| 39cbd7114e | |||
| a16ab4abfa | |||
| 6e12d2638d | |||
| 0d3c42beb2 | |||
| 66f65b25b4 | |||
| 3f85a93a03 | |||
| e9e9155366 | |||
| 30c9d3bce5 | |||
| dba5d90b94 | |||
| 0705659f3e | |||
| 4815651a1d | |||
| 71ebdc5d80 | |||
| 49ac3218f7 | |||
| 1c0c8080bf | |||
| 46f6c09739 | |||
| 93eb2d22b3 | |||
| c4d2524922 | |||
| 7482edde0a | |||
| 09b9a02284 | |||
| f895963ab9 | |||
| 637ab43626 | |||
| 811bb7ab44 | |||
| a65d330256 | |||
| 04cff622b2 | |||
| b2f260d04d | |||
| adda7bc6ac | |||
| fb936c45b6 | |||
| d0ade211ad | |||
| 90397f8029 | |||
| 7ee89d9e58 | |||
| 050fcec835 | |||
| f714a99108 | |||
| 5623c33d04 | |||
| 8d937b1e5f | |||
| 1613b12765 | |||
| db13c1b51e | |||
| 78e577114e | |||
| f5999bef99 | |||
| 1284a8f6aa | |||
| 1a89eec108 | |||
| dfb9b81c8b | |||
| 68ca55c82e | |||
| f68a338dec | |||
| 435170659e | |||
| 1a0e29e6dd | |||
| c9cf7d3ae8 | |||
| 0d4104b629 | |||
| c361682cab | |||
| 55b81a3c8f | |||
| c5e1ed8e86 | |||
| 4baf8f0d55 | |||
| 5a55397ef8 | |||
| 05200a195b | |||
| 7167849d6a | |||
| edfa90d820 | |||
| 3511452d93 | |||
| 1408f358c7 | |||
| 0297e1d22b | |||
| 10a17b0146 | |||
| bba60ea45d | |||
| 64e1e7da3e | |||
| 14e5bee7d6 | |||
| 2576987c44 | |||
| ea7d6c48db | |||
| 52f37fc3ed | |||
| 8cb0816f24 | |||
| ca59f02f4f | |||
| 7af761c5f6 | |||
| f2b75fc184 | |||
| 3bf457caef | |||
| 90b5596b76 | |||
| 93c0d6c83b | |||
| 9b5aa5f901 | |||
| bad92d3569 | |||
| 1f0e4d2260 | |||
| d460dc59f5 | |||
| eb64695697 | |||
| 308bebeb48 | |||
| bd71866342 | |||
| b55d58ec30 | |||
| 2cf4ef54c3 | |||
| 34a5e8bac1 | |||
| 502ec8ba2e | |||
| ac25897e9e | |||
| c44824c89d | |||
| 673de84936 | |||
| 78aa9c6ddc | |||
| ea2e5b9747 | |||
| ea1a33b51f | |||
| 7a441d50b9 | |||
| 364b74568e | |||
| 5f5cb1f55b | |||
| 4479fcd655 | |||
| ad56672819 | |||
| b4bcabbb9e | |||
| aaf1a1989b | |||
| 2025e366d2 | |||
| 201d1ce4e4 | |||
| 2d1e27926d | |||
| e449011f90 | |||
| 84b84453a7 | |||
| 00cee2d2fd | |||
| 78cce1970e | |||
| 977a822cc8 | |||
| 1558970619 | |||
| 86de211fa8 | |||
| 2923c6504b | |||
| 3e23950519 | |||
| 92eb32b4da | |||
| 754bd7380f | |||
| d32cda3663 | |||
| e175ea5c12 | |||
| 167c97120d | |||
| c8d2e8c985 | |||
| e73bd632f5 | |||
| 2a15434919 | |||
| b2c375861b | |||
| ae6fec335d | |||
| 16302ec6e4 | |||
| 4d03e1bb4e | |||
| 69ce669b4c | |||
| c512b422e7 | |||
| 2ad6541c3e | |||
| 379326d7c1 | |||
| 1359f5d210 | |||
| 64858ec6fe | |||
| 166175192d | |||
| 97de1d1c3f | |||
| 563792bd0a | |||
| 076db4c3eb | |||
| 02e1de8ffa | |||
| cc01bd18b2 | |||
| 2478adbaa2 | |||
| 45b177d0fb | |||
| f61e4f08a4 | |||
| c0deafcd69 | |||
| 0ad8b9e8e5 | |||
| eea6b0392d | |||
| 9845612c68 | |||
| d46ec1d3d5 | |||
| f1a65f53bc | |||
| 1410b72362 | |||
| e9245b522f | |||
| 636eb0f139 | |||
| 8645ef01fd | |||
| 18867bde89 | |||
| d061a6c4bf | |||
| e2ec76275a | |||
| 767bace201 | |||
| dbed559799 | |||
| 305d9cd581 | |||
| 212df695f2 | |||
| 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 |
@@ -1,9 +0,0 @@
|
||||
__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
|
||||
|
||||
@@ -8,6 +8,15 @@ LEON_LANG=en-US
|
||||
LEON_HOST=http://localhost
|
||||
LEON_PORT=1337
|
||||
|
||||
# Enable/disable LLM
|
||||
LEON_LLM=true
|
||||
# LLM provider
|
||||
LEON_LLM_PROVIDER=local
|
||||
# LLM provider API key (if not local)
|
||||
LEON_LLM_PROVIDER_API_KEY=
|
||||
# Enable/disable LLM Natural Language Generation
|
||||
LEON_LLM_NLG=true
|
||||
|
||||
# Time zone (current one by default)
|
||||
LEON_TIME_ZONE=
|
||||
|
||||
|
||||
+7
-4
@@ -18,10 +18,7 @@
|
||||
"browser": true
|
||||
},
|
||||
"parserOptions": {
|
||||
"ecmaVersion": 2021
|
||||
},
|
||||
"globals": {
|
||||
"io": true
|
||||
"ecmaVersion": "latest"
|
||||
},
|
||||
"plugins": ["@typescript-eslint", "unicorn", "import"],
|
||||
"ignorePatterns": "*.spec.js",
|
||||
@@ -67,6 +64,12 @@
|
||||
]
|
||||
},
|
||||
"overrides": [
|
||||
{
|
||||
"files": ["skills/**/*.ts"],
|
||||
"rules": {
|
||||
"import/order": "off"
|
||||
}
|
||||
},
|
||||
{
|
||||
"files": ["*.ts"],
|
||||
"rules": {
|
||||
|
||||
+5
-19
@@ -77,22 +77,6 @@ npm run dev:server
|
||||
npm run dev:app
|
||||
```
|
||||
|
||||
### Docker
|
||||
|
||||
```sh
|
||||
# Clone the repository
|
||||
git clone https://github.com/leon-ai/leon.git leon
|
||||
|
||||
# Go to the project root
|
||||
cd leon
|
||||
|
||||
# Build
|
||||
npm run docker:build
|
||||
|
||||
# Run the development server and the development web app
|
||||
npm run docker:dev
|
||||
```
|
||||
|
||||
## Versioning
|
||||
|
||||
- We use [Semantic Versioning](https://semver.org) for releases.
|
||||
@@ -123,11 +107,13 @@ Types define which kind of changes you made to the project.
|
||||
|
||||
Scopes define high-level nodes of Leon.
|
||||
|
||||
- web app
|
||||
- bridge/python
|
||||
- bridge/nodejs
|
||||
- hotword
|
||||
- scripts
|
||||
- server
|
||||
- tcp server
|
||||
- python bridge
|
||||
- hotword
|
||||
- web app
|
||||
- skill/skill_name
|
||||
|
||||
### Examples
|
||||
|
||||
@@ -16,7 +16,6 @@ If the bug is related to the setup, please submit the issue at: https://github.c
|
||||
- OS (or browser) version:
|
||||
- Node.js version:
|
||||
- Complete "leon check" (or "npm run check") output:
|
||||
- (if using Docker) Complete "npm run docker:check" output:
|
||||
- (optional) Leon skill version:
|
||||
|
||||
### Expected Behavior
|
||||
|
||||
@@ -1,13 +0,0 @@
|
||||
version: 2
|
||||
updates:
|
||||
- package-ecosystem: 'npm'
|
||||
directory: '/'
|
||||
schedule:
|
||||
interval: 'weekly'
|
||||
day: 'friday'
|
||||
time: '22:00'
|
||||
commit-message:
|
||||
prefix: 'chore'
|
||||
include: 'scope'
|
||||
reviewers:
|
||||
- 'louistiti'
|
||||
@@ -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,11 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
# @see https://github.com/actions/runner-images/tree/main/images/macos
|
||||
# Use macos-12 instead of macos-latest because the latter use ARM64 (M1) architecture
|
||||
os: [ubuntu-20.04, macos-12]
|
||||
# Temporarily disable Windows release
|
||||
# os: [ubuntu-20.04, macos-12, windows-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
||||
@@ -13,7 +13,9 @@ jobs:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [ubuntu-20.04, macos-latest, windows-latest]
|
||||
# @see https://github.com/actions/runner-images/tree/main/images/macos
|
||||
# Use macos-12 instead of macos-latest because the latter use ARM64 (M1) architecture
|
||||
os: [ubuntu-20.04, macos-12, windows-latest]
|
||||
|
||||
runs-on: ${{ matrix.os }}
|
||||
|
||||
|
||||
+5
-4
@@ -8,7 +8,6 @@ __pycache__/
|
||||
test/coverage/
|
||||
**/tmp/*
|
||||
**/src/.venv/*
|
||||
downloads/*
|
||||
logs/*
|
||||
core/config/**/*.json
|
||||
bin/coqui/*
|
||||
@@ -21,17 +20,19 @@ package-lock.json
|
||||
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
|
||||
!core/data/models/llm/.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
|
||||
core/data/models/llm/*
|
||||
package.json.backup
|
||||
.python-version
|
||||
schemas/**/*.json
|
||||
|
||||
+1
-1
@@ -6,6 +6,6 @@ if ! [ -x "$(command -v npm)" ]; then
|
||||
echo "If you use a version manager tool such as nvm and a git GUI such as GitKraken, please read: https://typicode.github.io/husky/#/?id=command-not-found" >&2
|
||||
exit 1
|
||||
else
|
||||
npx ts-node scripts/commit-msg.js
|
||||
npx tsx scripts/commit-msg.js
|
||||
fi
|
||||
|
||||
|
||||
@@ -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._
|
||||
|
||||
-60
@@ -1,60 +0,0 @@
|
||||
FROM ubuntu:20.04
|
||||
ENV IS_DOCKER true
|
||||
|
||||
# Replace shell with bash so we can source files
|
||||
RUN rm /bin/sh && ln -s /bin/bash /bin/sh
|
||||
|
||||
# Set debconf to run non-interactively
|
||||
RUN echo 'debconf debconf/frontend select Noninteractive' | debconf-set-selections
|
||||
|
||||
# Install base dependencies
|
||||
RUN apt-get update && apt-get install --yes -q --no-install-recommends \
|
||||
apt-transport-https \
|
||||
build-essential \
|
||||
ca-certificates \
|
||||
curl \
|
||||
git \
|
||||
wget \
|
||||
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
|
||||
USER docker
|
||||
WORKDIR /home/docker
|
||||
|
||||
# Install Node.js with nvm
|
||||
ENV NVM_DIR /home/docker/.nvm
|
||||
ENV NODE_VERSION v16.18.0
|
||||
|
||||
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 Leon
|
||||
WORKDIR /home/docker/leon
|
||||
USER root
|
||||
RUN chown -R docker /home/docker/leon
|
||||
USER docker
|
||||
COPY --chown=docker ./ ./
|
||||
RUN npm install
|
||||
RUN npm run build
|
||||
|
||||
CMD ["npm", "start"]
|
||||
@@ -36,7 +36,7 @@ _<p align="center">Your open-source personal assistant.</p>_
|
||||
|
||||
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!
|
||||
|
||||
I wish one day I could work on Leon full time, but it's not possible at the moment as I need to pay my bills. 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.
|
||||
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 🙂
|
||||
|
||||
@@ -50,24 +50,25 @@ 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, translation, sentiment analysis and so on...
|
||||
- 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.
|
||||
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.
|
||||
|
||||
<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>
|
||||
---
|
||||
|
||||
## 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>
|
||||
|
||||
---
|
||||
|
||||
@@ -151,19 +152,6 @@ leon start
|
||||
# Hooray! Leon is running
|
||||
```
|
||||
|
||||
### Docker Installation
|
||||
|
||||
```sh
|
||||
# Install Leon
|
||||
leon create birth --docker
|
||||
|
||||
# Run
|
||||
leon start
|
||||
|
||||
# Go to http://localhost:1337
|
||||
# Hooray! Leon is running
|
||||
```
|
||||
|
||||
## 📚 Documentation
|
||||
|
||||
For full documentation, visit [docs.getleon.ai](https://docs.getleon.ai).
|
||||
@@ -225,7 +213,7 @@ You'll find a write-up on this [blog post](https://blog.getleon.ai/the-story-beh
|
||||
<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>
|
||||
|
||||
+37
-6
@@ -1,4 +1,4 @@
|
||||
@import url(https://fonts.googleapis.com/css?family=Open+Sans:400,600,700,800);
|
||||
@import url(https://fonts.googleapis.com/css?family=Source+Sans+Pro:400,600,700);
|
||||
|
||||
html,
|
||||
body,
|
||||
@@ -83,7 +83,6 @@ video {
|
||||
padding: 0;
|
||||
border: 0;
|
||||
font-size: 100%;
|
||||
font: inherit;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
@@ -142,7 +141,21 @@ ul li {
|
||||
body {
|
||||
color: var(--white-color);
|
||||
background-color: var(--black-color);
|
||||
font-family: 'Open Sans', sans-serif;
|
||||
font-family:
|
||||
'Source Sans Pro',
|
||||
system-ui,
|
||||
-apple-system,
|
||||
BlinkMacSystemFont,
|
||||
'Segoe UI',
|
||||
Roboto,
|
||||
'Helvetica Neue',
|
||||
Arial,
|
||||
'Noto Sans',
|
||||
sans-serif,
|
||||
'Apple Color Emoji',
|
||||
'Segoe UI Emoji',
|
||||
'Segoe UI Symbol',
|
||||
'Noto Color Emoji';
|
||||
font-weight: 400;
|
||||
}
|
||||
body > * {
|
||||
@@ -187,6 +200,7 @@ footer {
|
||||
text-align: center;
|
||||
left: 50%;
|
||||
bottom: 0;
|
||||
line-height: 18px;
|
||||
transform: translate(-50%, -50%);
|
||||
}
|
||||
|
||||
@@ -202,9 +216,10 @@ input {
|
||||
padding-right: 39px;
|
||||
}
|
||||
|
||||
small {
|
||||
#tip {
|
||||
color: var(--white-color);
|
||||
font-size: 0.7em;
|
||||
line-height: 22px;
|
||||
font-size: 1.02em;
|
||||
}
|
||||
|
||||
.hide {
|
||||
@@ -287,6 +302,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;
|
||||
@@ -297,6 +323,9 @@ small {
|
||||
text-align: left;
|
||||
opacity: 0;
|
||||
animation: fadeIn 0.2s ease-in forwards;
|
||||
overflow: hidden;
|
||||
font-size: 1.4rem;
|
||||
line-height: 2rem;
|
||||
}
|
||||
#feed .me .bubble {
|
||||
background-color: #1c75db;
|
||||
@@ -331,7 +360,9 @@ small {
|
||||
padding: 2px 8px;
|
||||
font-size: inherit;
|
||||
cursor: pointer;
|
||||
transition: background-color 0.2s, color 0.2s;
|
||||
transition:
|
||||
background-color 0.2s,
|
||||
color 0.2s;
|
||||
}
|
||||
.suggestion:hover {
|
||||
color: var(--black-color);
|
||||
|
||||
+5
-4
@@ -1,4 +1,4 @@
|
||||
<!DOCTYPE html>
|
||||
<!doctype html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@@ -27,15 +27,16 @@
|
||||
</div>
|
||||
<label for="utterance"></label>
|
||||
<input type="text" id="utterance" autocomplete="off" autofocus />
|
||||
<small>
|
||||
<span id="tip">
|
||||
Use <kbd>↑</kbd> <kbd>↓</kbd> to browse history; <kbd>↵</kbd> to
|
||||
submit;
|
||||
<kbd>alt + c to listen.</kbd>
|
||||
</small>
|
||||
<kbd>alt+c to listen.</kbd>
|
||||
</span>
|
||||
</div>
|
||||
</main>
|
||||
<footer>
|
||||
<div id="logo"></div>
|
||||
<br />
|
||||
<div id="version">
|
||||
<small>v</small>
|
||||
</div>
|
||||
|
||||
@@ -1,3 +1,16 @@
|
||||
import '@fontsource/source-sans-pro/200.css'
|
||||
import '@fontsource/source-sans-pro/300.css'
|
||||
import '@fontsource/source-sans-pro/400.css'
|
||||
import '@fontsource/source-sans-pro/600.css'
|
||||
import '@fontsource/source-sans-pro/700.css'
|
||||
import '@fontsource/source-sans-pro/900.css'
|
||||
import 'remixicon/fonts/remixicon.css'
|
||||
import '@leon-ai/aurora/style.css'
|
||||
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { createElement } from 'react'
|
||||
import * as auroraComponents from '@leon-ai/aurora'
|
||||
|
||||
export default class Chatbot {
|
||||
constructor() {
|
||||
this.et = new EventTarget()
|
||||
@@ -60,6 +73,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 +108,29 @@ export default class Chatbot {
|
||||
|
||||
this.feed.appendChild(container).appendChild(bubble)
|
||||
|
||||
const root = createRoot(container)
|
||||
|
||||
const render = (component) => {
|
||||
if (component) {
|
||||
const reactComponent = auroraComponents[component.component]
|
||||
|
||||
if (
|
||||
component.props?.children &&
|
||||
Array.isArray(component.props.children)
|
||||
) {
|
||||
component.props.children = component.props.children.map((child) => {
|
||||
return render(child)
|
||||
})
|
||||
}
|
||||
|
||||
return createElement(reactComponent, component.props)
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof string === 'object') {
|
||||
root.render(render(string))
|
||||
}
|
||||
|
||||
if (save) {
|
||||
this.saveBubble(who, string)
|
||||
}
|
||||
+27
-4
@@ -1,6 +1,7 @@
|
||||
import { io } from 'socket.io-client'
|
||||
|
||||
import Chatbot from './chatbot'
|
||||
import { INIT_MESSAGES } from './constants'
|
||||
|
||||
export default class Client {
|
||||
constructor(client, serverUrl, input, res) {
|
||||
@@ -31,6 +32,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 +59,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 +86,10 @@ export default class Client {
|
||||
cb('string-received')
|
||||
})
|
||||
|
||||
this.socket.on('widget', (data) => {
|
||||
this.chatbot.createBubble('leon', data)
|
||||
})
|
||||
|
||||
this.socket.on('audio-forwarded', (data, cb) => {
|
||||
const ctx = new AudioContext()
|
||||
const source = ctx.createBufferSource()
|
||||
@@ -108,10 +135,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,18 @@
|
||||
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!`
|
||||
],
|
||||
[
|
||||
`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!`
|
||||
]
|
||||
]
|
||||
+3
-1
@@ -2,6 +2,7 @@ import dns from 'node:dns'
|
||||
|
||||
import dotenv from 'dotenv'
|
||||
import { defineConfig } from 'vite'
|
||||
import react from '@vitejs/plugin-react'
|
||||
|
||||
dotenv.config()
|
||||
|
||||
@@ -20,5 +21,6 @@ export default defineConfig({
|
||||
},
|
||||
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,51 @@
|
||||
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,
|
||||
new_utterance,
|
||||
current_entities,
|
||||
entities,
|
||||
current_resolvers,
|
||||
resolvers,
|
||||
slots,
|
||||
extra_context_data
|
||||
} = INTENT_OBJECT
|
||||
|
||||
const params: ActionParams = {
|
||||
lang,
|
||||
utterance,
|
||||
new_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,9 @@
|
||||
import { type ButtonProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class Button extends WidgetComponent<ButtonProps> {
|
||||
constructor(props: ButtonProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type CardProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class Card extends WidgetComponent<CardProps> {
|
||||
constructor(props: CardProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type CheckboxProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class Checkbox extends WidgetComponent<CheckboxProps> {
|
||||
constructor(props: CheckboxProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type CircularProgressProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class CircularProgress extends WidgetComponent<CircularProgressProps> {
|
||||
constructor(props: CircularProgressProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type FlexboxProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class Flexbox extends WidgetComponent<FlexboxProps> {
|
||||
constructor(props: FlexboxProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type IconButtonProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class IconButton extends WidgetComponent<IconButtonProps> {
|
||||
constructor(props: IconButtonProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type IconProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class Icon extends WidgetComponent<IconProps> {
|
||||
constructor(props: IconProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type ImageProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class Image extends WidgetComponent<ImageProps> {
|
||||
constructor(props: ImageProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
export * from './button'
|
||||
export * from './card'
|
||||
export * from './checkbox'
|
||||
export * from './circular-progress'
|
||||
export * from './flexbox'
|
||||
export * from './icon'
|
||||
export * from './icon-button'
|
||||
export * from './image'
|
||||
export * from './link'
|
||||
export * from './list'
|
||||
export * from './list-header'
|
||||
export * from './list-item'
|
||||
export * from './loader'
|
||||
export * from './progress'
|
||||
export * from './radio'
|
||||
export * from './radio-group'
|
||||
export * from './scroll-container'
|
||||
export * from './select'
|
||||
export * from './select-option'
|
||||
export * from './slider'
|
||||
export * from './status'
|
||||
export * from './switch'
|
||||
export * from './tab'
|
||||
export * from './tab-content'
|
||||
export * from './tab-group'
|
||||
export * from './tab-list'
|
||||
export * from './text'
|
||||
export * from './text-input'
|
||||
export * from './widget-wrapper'
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type LinkProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class Link extends WidgetComponent<LinkProps> {
|
||||
constructor(props: LinkProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type ListHeaderProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class ListHeader extends WidgetComponent<ListHeaderProps> {
|
||||
constructor(props: ListHeaderProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type ListItemProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class ListItem extends WidgetComponent<ListItemProps> {
|
||||
constructor(props: ListItemProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type ListProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class List extends WidgetComponent<ListProps> {
|
||||
constructor(props: ListProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type LoaderProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class Loader extends WidgetComponent<LoaderProps> {
|
||||
constructor(props: LoaderProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type ProgressProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class Progress extends WidgetComponent<ProgressProps> {
|
||||
constructor(props: ProgressProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type RadioGroupProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class RadioGroup extends WidgetComponent<RadioGroupProps> {
|
||||
constructor(props: RadioGroupProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type RadioProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class Radio extends WidgetComponent<RadioProps> {
|
||||
constructor(props: RadioProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type ScrollContainerProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class ScrollContainer extends WidgetComponent<ScrollContainerProps> {
|
||||
constructor(props: ScrollContainerProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type SelectOptionProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class SelectOption extends WidgetComponent<SelectOptionProps> {
|
||||
constructor(props: SelectOptionProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type SelectProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class Select extends WidgetComponent<SelectProps> {
|
||||
constructor(props: SelectProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type SliderProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class Slider extends WidgetComponent<SliderProps> {
|
||||
constructor(props: SliderProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type StatusProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class Status extends WidgetComponent<StatusProps> {
|
||||
constructor(props: StatusProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type SwitchProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class Switch extends WidgetComponent<SwitchProps> {
|
||||
constructor(props: SwitchProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type TabContentProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class TabContent extends WidgetComponent<TabContentProps> {
|
||||
constructor(props: TabContentProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type TabGroupProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class TabGroup extends WidgetComponent<TabGroupProps> {
|
||||
constructor(props: TabGroupProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type TabListProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class TabList extends WidgetComponent<TabListProps> {
|
||||
constructor(props: TabListProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type TabProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class Tab extends WidgetComponent<TabProps> {
|
||||
constructor(props: TabProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type TextInputProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class TextInput extends WidgetComponent<TextInputProps> {
|
||||
constructor(props: TextInputProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type TextProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class Text extends WidgetComponent<TextProps> {
|
||||
constructor(props: TextProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { type WidgetWrapperProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '../widget-component'
|
||||
|
||||
export class WidgetWrapper extends WidgetComponent<WidgetWrapperProps> {
|
||||
constructor(props: WidgetWrapperProps) {
|
||||
super(props)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,134 @@
|
||||
import type {
|
||||
AnswerData,
|
||||
AnswerInput,
|
||||
AnswerOutput,
|
||||
AnswerConfig
|
||||
} from '@sdk/types'
|
||||
import { INTENT_OBJECT, SKILL_CONFIG } from '@bridge/constants'
|
||||
import { WidgetWrapper } from '@sdk/aurora'
|
||||
|
||||
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 = new WidgetWrapper({
|
||||
...answerInput.widget.wrapperProps,
|
||||
children: [answerInput.widget.render()]
|
||||
})
|
||||
}
|
||||
|
||||
// "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,100 @@
|
||||
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>
|
||||
|
||||
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
|
||||
|
||||
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
|
||||
|
||||
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,39 @@
|
||||
/**
|
||||
* Action types
|
||||
*/
|
||||
import type {
|
||||
ActionParams,
|
||||
IntentObject,
|
||||
SkillAnswerCoreData,
|
||||
SkillAnswerOutput
|
||||
} from '@/core/brain/types'
|
||||
import type { SkillAnswerConfigSchema } from '@/schemas/skill-schemas'
|
||||
|
||||
import type { Widget } from '@sdk/widget'
|
||||
|
||||
export type { ActionParams, IntentObject }
|
||||
|
||||
export * from '@/core/nlp/types'
|
||||
|
||||
export type ActionFunction = (params: ActionParams) => Promise<void>
|
||||
|
||||
/**
|
||||
* Answer types
|
||||
*/
|
||||
export interface Answer {
|
||||
key?: string
|
||||
widget?: Widget
|
||||
data?: AnswerData
|
||||
core?: SkillAnswerCoreData
|
||||
}
|
||||
export interface TextAnswer extends Answer {
|
||||
key: string
|
||||
}
|
||||
export interface WidgetAnswer extends Answer {
|
||||
widget: Widget
|
||||
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 WidgetComponent<T = unknown> {
|
||||
public readonly component: string
|
||||
public readonly props: T
|
||||
|
||||
protected constructor(props: T) {
|
||||
this.component = this.constructor.name
|
||||
this.props = props
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,22 @@
|
||||
import { type WidgetWrapperProps } from '@leon-ai/aurora'
|
||||
|
||||
import { WidgetComponent } from '@sdk/widget-component'
|
||||
|
||||
export interface WidgetOptions<T = unknown> {
|
||||
wrapperProps?: Omit<WidgetWrapperProps, 'children'>
|
||||
params: T
|
||||
}
|
||||
|
||||
export abstract class Widget<T = unknown> {
|
||||
public wrapperProps: WidgetOptions<T>['wrapperProps']
|
||||
public params: WidgetOptions<T>['params']
|
||||
|
||||
protected constructor(options: WidgetOptions<T>) {
|
||||
if (options?.wrapperProps) {
|
||||
this.wrapperProps = options.wrapperProps
|
||||
}
|
||||
this.params = options.params
|
||||
}
|
||||
|
||||
public abstract render(): WidgetComponent<unknown>
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
export const VERSION = '1.2.0'
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"outDir": "./dist/bin",
|
||||
"rootDir": "../../",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@@/*": ["../../*"],
|
||||
"@/*": ["../../server/src/*"],
|
||||
"@server/*": ["../../server/src/*"],
|
||||
"@bridge/*": ["./src/*"],
|
||||
"@sdk/*": ["./src/sdk/*"]
|
||||
},
|
||||
"exactOptionalPropertyTypes": false,
|
||||
"declaration": 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__
|
||||
+35
-27
@@ -1,37 +1,45 @@
|
||||
#!/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'],
|
||||
'new_utterance': INTENT_OBJECT['new_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,19 @@
|
||||
from typing import Optional, TypedDict, Union, Literal, Any
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class ButtonProps(TypedDict, total=False):
|
||||
type: Optional[Union[Literal['button'], Literal['submit'], Literal['reset']]]
|
||||
iconName: Optional[str]
|
||||
iconPosition: Optional[Union[Literal['left'], Literal['right']]]
|
||||
secondary: Optional[bool]
|
||||
danger: Optional[bool]
|
||||
light: Optional[bool]
|
||||
disabled: Optional[bool]
|
||||
loading: Optional[bool]
|
||||
children: Any
|
||||
|
||||
|
||||
class Button(WidgetComponent[ButtonProps]):
|
||||
def __init__(self, props: ButtonProps):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class Card(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class Checkbox(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class CircularProgress(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class Flexbox(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class Icon(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class IconButton(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class Image(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class Link(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class List(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class ListHeader(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class ListItem(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class Loader(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class Progress(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class Radio(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class RadioGroup(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class ScrollContainer(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class Select(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class SelectOption(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class Slider(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class Switch(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class Tab(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class TabContent(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class TabGroup(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class TabList(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class Text(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,6 @@
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class TextInput(WidgetComponent[dict]):
|
||||
def __init__(self, props: dict):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,16 @@
|
||||
from typing import Optional, TypedDict, List
|
||||
from ..widget_component import WidgetComponent
|
||||
|
||||
|
||||
class WidgetWrapperProps(TypedDict, total=False):
|
||||
noPadding: Optional[bool]
|
||||
paddingTop: Optional[bool]
|
||||
paddingBottom: Optional[bool]
|
||||
paddingLeft: Optional[bool]
|
||||
paddingRight: Optional[bool]
|
||||
children: List[WidgetComponent]
|
||||
|
||||
|
||||
class WidgetWrapper(WidgetComponent[WidgetWrapperProps]):
|
||||
def __init__(self, props: WidgetWrapperProps):
|
||||
super().__init__(props)
|
||||
@@ -0,0 +1,98 @@
|
||||
import random
|
||||
import sys
|
||||
from typing import Union
|
||||
from time import sleep
|
||||
import json
|
||||
|
||||
from .aurora.widget_wrapper import WidgetWrapper
|
||||
|
||||
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')
|
||||
}
|
||||
}
|
||||
|
||||
widget = answer_input.get('widget')
|
||||
if widget is not None:
|
||||
output['output']['widget'] = WidgetWrapper({
|
||||
**widget.wrapper_props,
|
||||
'children': [widget.render()]
|
||||
}).__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
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user