Compare commits
55 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 9c5d2ea0c8 | |||
| 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 | |||
| 31e44cde37 | |||
| 248dbb84f0 | |||
| 609fb676e4 | |||
| 5d866664b2 | |||
| ba09ef10b1 | |||
| cb2db411ce | |||
| f179e1070c | |||
| fdeff12762 |
+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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -21,10 +21,12 @@ package-lock.json
|
||||
npm-debug.log
|
||||
debug.log
|
||||
.env
|
||||
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
|
||||
@@ -33,3 +35,4 @@ 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._
|
||||
|
||||
@@ -56,18 +56,19 @@ Here is how LLMs may help Leon in the future:
|
||||
|
||||
### 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>
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -0,0 +1,2 @@
|
||||
package-lock=false
|
||||
save-exact=true
|
||||
Vendored
@@ -0,0 +1,15 @@
|
||||
{
|
||||
"name": "leon-nodejs-bridge",
|
||||
"description": "Leon's Node.js bridge to communicate between the core and skills made with JavaScript",
|
||||
"main": "dist/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"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,5 @@
|
||||
import { VERSION } from './version'
|
||||
|
||||
console.log('[WIP] Node.js bridge', VERSION)
|
||||
|
||||
// TODO
|
||||
@@ -0,0 +1 @@
|
||||
export const VERSION = '0.0.0'
|
||||
@@ -0,0 +1,23 @@
|
||||
{
|
||||
"extends": "@tsconfig/node16-strictest/tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist",
|
||||
"baseUrl": ".",
|
||||
"paths": {
|
||||
"@/*": ["./src/*"]
|
||||
},
|
||||
"ignoreDeprecations": "5.0",
|
||||
"allowJs": true,
|
||||
"checkJs": false,
|
||||
"resolveJsonModule": true
|
||||
},
|
||||
"ts-node": {
|
||||
"swc": true,
|
||||
"require": ["tsconfig-paths/register"],
|
||||
"files": true
|
||||
},
|
||||
"files": [],
|
||||
"include": ["src/**/*"],
|
||||
"exclude": ["dist"]
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
from cx_Freeze import setup, Executable
|
||||
import requests.certs
|
||||
|
||||
from version import __version__
|
||||
|
||||
@@ -8,7 +9,8 @@ options = {
|
||||
'includes': [
|
||||
'bs4',
|
||||
'pytube'
|
||||
]
|
||||
],
|
||||
'include_files': [(requests.certs.where(), 'cacert.pem')]
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1 +1 @@
|
||||
__version__ = '1.0.0'
|
||||
__version__ = '1.0.1'
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/voice-config-schemas/amazon.json",
|
||||
"credentials": {
|
||||
"accessKeyId": "",
|
||||
"secretAccessKey": ""
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/voice-config-schemas/google-cloud.json",
|
||||
"type": "service_account",
|
||||
"project_id": "",
|
||||
"private_key_id": "",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/voice-config-schemas/watson-stt.json",
|
||||
"apikey": "",
|
||||
"url": "https://stream.watsonplatform.net/speech-to-text/api"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/voice-config-schemas/watson-tts.json",
|
||||
"apikey": "",
|
||||
"url": "https://stream.watsonplatform.net/text-to-speech/api"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/global-data/global-answers.json",
|
||||
"answers": {
|
||||
"success": {},
|
||||
"errors": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/global-data/global-answers.json",
|
||||
"answers": {
|
||||
"success": {},
|
||||
"errors": {
|
||||
|
||||
@@ -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": {
|
||||
|
||||
+6
-2
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "leon",
|
||||
"version": "1.0.0-beta.8+dev",
|
||||
"version": "1.0.0-beta.8",
|
||||
"description": "Server, skills and web app of the Leon personal assistant",
|
||||
"author": {
|
||||
"name": "Louis Grenard",
|
||||
@@ -47,9 +47,11 @@
|
||||
"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",
|
||||
@@ -57,6 +59,7 @@
|
||||
"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",
|
||||
"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",
|
||||
@@ -109,6 +112,7 @@
|
||||
"@types/archiver": "5.3.2",
|
||||
"@types/cli-spinner": "0.2.1",
|
||||
"@types/fluent-ffmpeg": "2.1.20",
|
||||
"@types/getos": "3.0.1",
|
||||
"@types/node": "18.7.13",
|
||||
"@types/node-wav": "0.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "5.55.0",
|
||||
|
||||
+69
-23
@@ -9,10 +9,13 @@ 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'
|
||||
@@ -29,8 +32,15 @@ import { SystemHelper } from '@/helpers/system-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,
|
||||
@@ -39,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,
|
||||
@@ -62,13 +73,16 @@ 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)
|
||||
: distPath
|
||||
|
||||
const { type: osType } = SystemHelper.getInformation()
|
||||
|
||||
@@ -76,7 +90,7 @@ BUILD_TARGETS.set('tcp-server', {
|
||||
* 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 })
|
||||
@@ -92,30 +106,58 @@ 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 tsconfigPath = path.join(NODEJS_BRIDGE_ROOT_PATH, 'tsconfig.json')
|
||||
const distMainFilePath = path.join(NODEJS_BRIDGE_DIST_PATH, 'main.js')
|
||||
const distRenamedMainFilePath = path.join(
|
||||
NODEJS_BRIDGE_DIST_PATH,
|
||||
NODEJS_BRIDGE_BIN_NAME
|
||||
)
|
||||
|
||||
await command(`tsc --project ${tsconfigPath}`, {
|
||||
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)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -143,7 +185,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.glob(`**/!(${archiveName})`, { cwd: distPath })
|
||||
}
|
||||
|
||||
await archive.finalize()
|
||||
})()
|
||||
|
||||
+67
-61
@@ -11,14 +11,16 @@ 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,
|
||||
PYTHON_BRIDGE_BIN_PATH,
|
||||
TCP_SERVER_BIN_PATH,
|
||||
TCP_SERVER_VERSION,
|
||||
PYTHON_BRIDGE_VERSION
|
||||
PYTHON_BRIDGE_VERSION,
|
||||
INSTANCE_ID
|
||||
} from '@/constants'
|
||||
|
||||
dotenv.config()
|
||||
@@ -31,7 +33,6 @@ dotenv.config()
|
||||
try {
|
||||
const nodeMinRequiredVersion = '16'
|
||||
const npmMinRequiredVersion = '8'
|
||||
const minimumRequiredRAM = 4
|
||||
const flitePath = 'bin/flite/flite'
|
||||
const coquiLanguageModelPath = 'bin/coqui/huge-vocabulary.scorer'
|
||||
const amazonPath = 'core/config/voice/amazon.json'
|
||||
@@ -43,34 +44,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 },
|
||||
@@ -116,6 +89,35 @@ 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
|
||||
},
|
||||
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')
|
||||
|
||||
@@ -124,8 +126,8 @@ dotenv.config()
|
||||
*/
|
||||
|
||||
LogHelper.info('Leon version')
|
||||
LogHelper.success(`${version}\n`)
|
||||
pastebinData.leonVersion = version
|
||||
LogHelper.success(`${LEON_VERSION}\n`)
|
||||
reportDataInput.leonVersion = LEON_VERSION
|
||||
|
||||
/**
|
||||
* Environment checking
|
||||
@@ -144,10 +146,10 @@ dotenv.config()
|
||||
}
|
||||
const totalRAMInGB = SystemHelper.getTotalRAM()
|
||||
|
||||
if (totalRAMInGB < minimumRequiredRAM) {
|
||||
if (Math.round(totalRAMInGB) < MINIMUM_REQUIRED_RAM) {
|
||||
report.can_run.v = false
|
||||
LogHelper.error(
|
||||
`Total RAM: ${totalRAMInGB} GB. Leon needs at least ${minimumRequiredRAM} GB of RAM`
|
||||
`Total RAM: ${totalRAMInGB} GB. Leon needs at least ${MINIMUM_REQUIRED_RAM} GB of RAM`
|
||||
)
|
||||
} else {
|
||||
LogHelper.success(`Total RAM: ${totalRAMInGB} GB`)
|
||||
@@ -162,8 +164,8 @@ dotenv.config()
|
||||
LogHelper.success(`${JSON.stringify(osInfo)}\n`)
|
||||
}
|
||||
|
||||
pastebinData.environment.osDetails = osInfo
|
||||
pastebinData.environment.totalRAMInGB = totalRAMInGB
|
||||
reportDataInput.environment.osDetails = osInfo
|
||||
reportDataInput.environment.totalRAMInGB = totalRAMInGB
|
||||
;(
|
||||
await Promise.all([
|
||||
command('node --version', { shell: true }),
|
||||
@@ -195,9 +197,9 @@ 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
|
||||
}
|
||||
}
|
||||
})
|
||||
@@ -207,7 +209,7 @@ dotenv.config()
|
||||
*/
|
||||
|
||||
LogHelper.success(`Python bridge version: ${PYTHON_BRIDGE_VERSION}`)
|
||||
pastebinData.pythonBridge.version = PYTHON_BRIDGE_VERSION
|
||||
reportDataInput.pythonBridge.version = PYTHON_BRIDGE_VERSION
|
||||
LogHelper.info('Executing a skill...')
|
||||
|
||||
try {
|
||||
@@ -224,16 +226,16 @@ dotenv.config()
|
||||
const executionEnd = Date.now()
|
||||
const executionTime = executionEnd - executionStart
|
||||
LogHelper.info(p.command)
|
||||
pastebinData.pythonBridge.command = p.command
|
||||
reportDataInput.pythonBridge.command = p.command
|
||||
LogHelper.success(p.stdout)
|
||||
pastebinData.pythonBridge.output = p.stdout
|
||||
reportDataInput.pythonBridge.output = p.stdout
|
||||
LogHelper.info(`Skill execution time: ${executionTime}ms\n`)
|
||||
pastebinData.pythonBridge.executionTime = `${executionTime}ms`
|
||||
reportDataInput.pythonBridge.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.pythonBridge.error = JSON.stringify(e)
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -241,7 +243,7 @@ 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...')
|
||||
|
||||
@@ -253,7 +255,7 @@ dotenv.config()
|
||||
]
|
||||
|
||||
LogHelper.info(tcpServerCommand)
|
||||
pastebinData.tcpServer.command = tcpServerCommand
|
||||
reportDataInput.tcpServer.command = tcpServerCommand
|
||||
|
||||
if (osInfo.platform === 'darwin') {
|
||||
LogHelper.info(
|
||||
@@ -280,7 +282,7 @@ dotenv.config()
|
||||
if (!ignoredWarnings.some((w) => newData.includes(w))) {
|
||||
tcpServerOutput += newData
|
||||
report.can_start_tcp_server.v = false
|
||||
pastebinData.tcpServer.error = newData
|
||||
reportDataInput.tcpServer.error = newData
|
||||
LogHelper.error(`Cannot start the TCP server: ${newData}`)
|
||||
}
|
||||
})
|
||||
@@ -292,16 +294,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}\n`
|
||||
`TCP server startup time: ${reportDataInput.tcpServer.startTime}\n`
|
||||
)
|
||||
|
||||
/**
|
||||
@@ -325,12 +327,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
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -354,12 +356,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
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -382,12 +384,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
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -525,7 +527,11 @@ 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')
|
||||
|
||||
@@ -533,11 +539,11 @@ dotenv.config()
|
||||
|
||||
try {
|
||||
const { data } = await axios.post('https://getleon.ai/api/report', {
|
||||
report: pastebinData
|
||||
report: reportDataInput
|
||||
})
|
||||
const { data: reportData } = data
|
||||
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}`)
|
||||
}
|
||||
|
||||
@@ -18,7 +18,7 @@ import { LogHelper } from '@/helpers/log-helper'
|
||||
'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}'
|
||||
'(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')
|
||||
|
||||
@@ -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...')
|
||||
|
||||
@@ -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')
|
||||
}
|
||||
@@ -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}`)
|
||||
}
|
||||
})()
|
||||
@@ -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(
|
||||
|
||||
@@ -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,159 @@
|
||||
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 prettyBytes from 'pretty-bytes'
|
||||
import prettyMilliseconds from 'pretty-ms'
|
||||
import extractZip from 'extract-zip'
|
||||
|
||||
import {
|
||||
BINARIES_FOLDER_NAME,
|
||||
GITHUB_URL,
|
||||
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 })
|
||||
])
|
||||
|
||||
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 = isPlatformDependent
|
||||
? path.resolve(distPath)
|
||||
: path.resolve(distPath, 'bin')
|
||||
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 (error) {
|
||||
throw new Error(`Failed to set up ${name}: ${error}`)
|
||||
}
|
||||
} 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')
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
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 prettyBytes from 'pretty-bytes'
|
||||
import prettyMilliseconds from 'pretty-ms'
|
||||
import extractZip from 'extract-zip'
|
||||
|
||||
import {
|
||||
BINARIES_FOLDER_NAME,
|
||||
GITHUB_URL,
|
||||
PYTHON_BRIDGE_DIST_PATH,
|
||||
TCP_SERVER_DIST_PATH,
|
||||
PYTHON_BRIDGE_BIN_NAME,
|
||||
TCP_SERVER_BIN_NAME,
|
||||
PYTHON_BRIDGE_VERSION,
|
||||
TCP_SERVER_VERSION
|
||||
} from '@/constants'
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
|
||||
/**
|
||||
* Set up Python binaries according to the given setup target
|
||||
* 1. Delete the existing dist binaries if already exist
|
||||
* 2. Download the latest Python binaries from GitHub releases
|
||||
* 3. Extract the downloaded ZIP file to the dist folder
|
||||
*/
|
||||
|
||||
const PYTHON_TARGETS = new Map()
|
||||
|
||||
PYTHON_TARGETS.set('python-bridge', {
|
||||
name: 'Python bridge',
|
||||
distPath: PYTHON_BRIDGE_DIST_PATH,
|
||||
archiveName: `${PYTHON_BRIDGE_BIN_NAME}-${BINARIES_FOLDER_NAME}.zip`,
|
||||
version: PYTHON_BRIDGE_VERSION
|
||||
})
|
||||
PYTHON_TARGETS.set('tcp-server', {
|
||||
name: 'TCP server',
|
||||
distPath: TCP_SERVER_DIST_PATH,
|
||||
archiveName: `${TCP_SERVER_BIN_NAME}-${BINARIES_FOLDER_NAME}.zip`,
|
||||
version: TCP_SERVER_VERSION
|
||||
})
|
||||
|
||||
const setupPythonBinaries = async (key) => {
|
||||
const { name, distPath, archiveName, version } = PYTHON_TARGETS.get(key)
|
||||
const buildPath = path.join(distPath, BINARIES_FOLDER_NAME)
|
||||
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 })
|
||||
])
|
||||
|
||||
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 fs.promises.rm(archivePath, { recursive: true, force: true })
|
||||
|
||||
LogHelper.success(`${name} ready`)
|
||||
} catch (error) {
|
||||
throw new Error(`Failed to set up ${name}: ${error}`)
|
||||
}
|
||||
}
|
||||
|
||||
export default async () => {
|
||||
await setupPythonBinaries('python-bridge')
|
||||
await setupPythonBinaries('tcp-server')
|
||||
}
|
||||
@@ -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 setupPythonBinaries from './setup-python-binaries'
|
||||
import setupBinaries from './setup-binaries'
|
||||
import createInstanceID from './create-instance-id'
|
||||
|
||||
// Do not load ".env" file because it is not created yet
|
||||
|
||||
@@ -20,10 +22,12 @@ import setupPythonBinaries from './setup-python-binaries'
|
||||
LoaderHelper.start()
|
||||
await Promise.all([setupCore(), setupSkillsConfig()])
|
||||
LoaderHelper.stop()
|
||||
await setupPythonBinaries()
|
||||
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!')
|
||||
|
||||
+43
-6
@@ -18,12 +18,28 @@ export const GITHUB_URL = 'https://github.com/leon-ai/leon'
|
||||
* Binaries / distribution
|
||||
*/
|
||||
export const BINARIES_FOLDER_NAME = SystemHelper.getBinariesFolderName()
|
||||
export const PYTHON_BRIDGE_DIST_PATH = path.join('bridges', 'python', 'dist')
|
||||
export const TCP_SERVER_DIST_PATH = path.join('tcp_server', 'dist')
|
||||
export const NODEJS_BRIDGE_ROOT_PATH = path.join('bridges', 'nodejs')
|
||||
export const PYTHON_BRIDGE_ROOT_PATH = path.join('bridges', 'python')
|
||||
export const TCP_SERVER_ROOT_PATH = path.join('tcp_server')
|
||||
|
||||
export const PYTHON_BRIDGE_SRC_PATH = path.join('bridges', 'python', 'src')
|
||||
export const TCP_SERVER_SRC_PATH = path.join('tcp_server', 'src')
|
||||
export const NODEJS_BRIDGE_DIST_PATH = path.join(
|
||||
NODEJS_BRIDGE_ROOT_PATH,
|
||||
'dist'
|
||||
)
|
||||
export const PYTHON_BRIDGE_DIST_PATH = path.join(
|
||||
PYTHON_BRIDGE_ROOT_PATH,
|
||||
'dist'
|
||||
)
|
||||
export const TCP_SERVER_DIST_PATH = path.join(TCP_SERVER_ROOT_PATH, 'dist')
|
||||
|
||||
export const NODEJS_BRIDGE_SRC_PATH = path.join(NODEJS_BRIDGE_ROOT_PATH, 'src')
|
||||
export const PYTHON_BRIDGE_SRC_PATH = path.join(PYTHON_BRIDGE_ROOT_PATH, 'src')
|
||||
export const TCP_SERVER_SRC_PATH = path.join(TCP_SERVER_ROOT_PATH, 'src')
|
||||
|
||||
const NODEJS_BRIDGE_VERSION_FILE_PATH = path.join(
|
||||
NODEJS_BRIDGE_SRC_PATH,
|
||||
'version.ts'
|
||||
)
|
||||
const PYTHON_BRIDGE_VERSION_FILE_PATH = path.join(
|
||||
PYTHON_BRIDGE_SRC_PATH,
|
||||
'version.py'
|
||||
@@ -32,6 +48,9 @@ const TCP_SERVER_VERSION_FILE_PATH = path.join(
|
||||
TCP_SERVER_SRC_PATH,
|
||||
'version.py'
|
||||
)
|
||||
export const [, NODEJS_BRIDGE_VERSION] = fs
|
||||
.readFileSync(NODEJS_BRIDGE_VERSION_FILE_PATH, 'utf8')
|
||||
.split("'")
|
||||
export const [, PYTHON_BRIDGE_VERSION] = fs
|
||||
.readFileSync(PYTHON_BRIDGE_VERSION_FILE_PATH, 'utf8')
|
||||
.split("'")
|
||||
@@ -39,6 +58,7 @@ export const [, TCP_SERVER_VERSION] = fs
|
||||
.readFileSync(TCP_SERVER_VERSION_FILE_PATH, 'utf8')
|
||||
.split("'")
|
||||
|
||||
export const NODEJS_BRIDGE_BIN_NAME = 'leon-nodejs-bridge.js'
|
||||
export const PYTHON_BRIDGE_BIN_NAME = 'leon-python-bridge'
|
||||
export const TCP_SERVER_BIN_NAME = 'leon-tcp-server'
|
||||
|
||||
@@ -52,6 +72,13 @@ export const PYTHON_BRIDGE_BIN_PATH = path.join(
|
||||
BINARIES_FOLDER_NAME,
|
||||
PYTHON_BRIDGE_BIN_NAME
|
||||
)
|
||||
export const NODEJS_BRIDGE_BIN_PATH = `${process.execPath} ${path.join(
|
||||
NODEJS_BRIDGE_DIST_PATH,
|
||||
'bin',
|
||||
NODEJS_BRIDGE_BIN_NAME
|
||||
)}`
|
||||
|
||||
export const LEON_VERSION = process.env['npm_package_version']
|
||||
|
||||
/**
|
||||
* spaCy models
|
||||
@@ -91,11 +118,11 @@ export const HAS_OVER_HTTP = process.env['LEON_OVER_HTTP'] === 'true'
|
||||
export const HTTP_API_KEY = process.env['LEON_HTTP_API_KEY']
|
||||
export const HTTP_API_LANG = process.env['LEON_HTTP_API_LANG']
|
||||
|
||||
export const HAS_LOGGER = process.env['LEON_LOGGER'] === 'true'
|
||||
|
||||
export const TCP_SERVER_HOST = process.env['LEON_PY_TCP_SERVER_HOST']
|
||||
export const TCP_SERVER_PORT = Number(process.env['LEON_PY_TCP_SERVER_PORT'])
|
||||
|
||||
export const IS_TELEMETRY_ENABLED = process.env['LEON_TELEMETRY'] === 'true'
|
||||
|
||||
/**
|
||||
* Paths
|
||||
*/
|
||||
@@ -108,3 +135,13 @@ export const SERVER_PATH = path.join(
|
||||
IS_PRODUCTION_ENV ? 'dist' : 'src'
|
||||
)
|
||||
export const TMP_PATH = path.join(SERVER_PATH, 'tmp')
|
||||
export const LEON_FILE_PATH = path.join('leon.json')
|
||||
|
||||
/**
|
||||
* Misc
|
||||
*/
|
||||
export const MINIMUM_REQUIRED_RAM = 4
|
||||
export const INSTANCE_ID = fs.existsSync(LEON_FILE_PATH)
|
||||
? JSON.parse(fs.readFileSync(LEON_FILE_PATH, 'utf8')).instanceID
|
||||
: null
|
||||
export const IS_GITPOD = process.env['GITPOD_WORKSPACE_URL'] !== undefined
|
||||
|
||||
@@ -9,15 +9,24 @@ import type {
|
||||
NERCustomEntity,
|
||||
NLUResult
|
||||
} from '@/core/nlp/types'
|
||||
import type { SkillConfigSchema } from '@/schemas/skill-schemas'
|
||||
import type { SkillConfigSchema, SkillSchema } from '@/schemas/skill-schemas'
|
||||
import type {
|
||||
BrainProcessResult,
|
||||
IntentObject,
|
||||
SkillResult
|
||||
} from '@/core/brain/types'
|
||||
import { SkillActionType, SkillOutputType } from '@/core/brain/types'
|
||||
import {
|
||||
SkillActionTypes,
|
||||
SkillBridges,
|
||||
SkillOutputTypes
|
||||
} from '@/core/brain/types'
|
||||
import { langs } from '@@/core/langs.json'
|
||||
import { HAS_TTS, PYTHON_BRIDGE_BIN_PATH, TMP_PATH } from '@/constants'
|
||||
import {
|
||||
HAS_TTS,
|
||||
PYTHON_BRIDGE_BIN_PATH,
|
||||
NODEJS_BRIDGE_BIN_PATH,
|
||||
TMP_PATH
|
||||
} from '@/constants'
|
||||
import { SOCKET_SERVER, TTS } from '@/core'
|
||||
import { LangHelper } from '@/helpers/lang-helper'
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
@@ -162,11 +171,11 @@ export default class Brain {
|
||||
*/
|
||||
private createIntentObject(
|
||||
nluResult: NLUResult,
|
||||
utteranceId: string,
|
||||
utteranceID: string,
|
||||
slots: IntentObject['slots']
|
||||
): IntentObject {
|
||||
return {
|
||||
id: utteranceId,
|
||||
id: utteranceID,
|
||||
lang: this._lang,
|
||||
domain: nluResult.classification.domain,
|
||||
skill: nluResult.classification.skill,
|
||||
@@ -190,7 +199,7 @@ export default class Brain {
|
||||
const obj = JSON.parse(data.toString())
|
||||
|
||||
if (typeof obj === 'object') {
|
||||
if (obj.output.type === SkillOutputType.Intermediate) {
|
||||
if (obj.output.type === SkillOutputTypes.Intermediate) {
|
||||
LogHelper.title(`${this.skillFriendlyName} skill`)
|
||||
LogHelper.info(data.toString())
|
||||
|
||||
@@ -258,7 +267,8 @@ export default class Brain {
|
||||
*/
|
||||
private async executeLogicActionSkill(
|
||||
nluResult: NLUResult,
|
||||
utteranceId: string,
|
||||
skillBridge: SkillSchema['bridge'],
|
||||
utteranceID: string,
|
||||
intentObjectPath: string
|
||||
): Promise<void> {
|
||||
// Ensure the process is empty (to be able to execute other processes outside of Brain)
|
||||
@@ -273,7 +283,7 @@ export default class Brain {
|
||||
|
||||
const intentObject = this.createIntentObject(
|
||||
nluResult,
|
||||
utteranceId,
|
||||
utteranceID,
|
||||
slots
|
||||
)
|
||||
|
||||
@@ -282,10 +292,20 @@ export default class Brain {
|
||||
intentObjectPath,
|
||||
JSON.stringify(intentObject)
|
||||
)
|
||||
this.skillProcess = spawn(
|
||||
`${PYTHON_BRIDGE_BIN_PATH} "${intentObjectPath}"`,
|
||||
{ shell: true }
|
||||
)
|
||||
|
||||
if (skillBridge === SkillBridges.Python) {
|
||||
this.skillProcess = spawn(
|
||||
`${PYTHON_BRIDGE_BIN_PATH} "${intentObjectPath}"`,
|
||||
{ shell: true }
|
||||
)
|
||||
} else if (skillBridge === SkillBridges.NodeJS) {
|
||||
this.skillProcess = spawn(
|
||||
`${NODEJS_BRIDGE_BIN_PATH} "${intentObjectPath}"`,
|
||||
{ shell: true }
|
||||
)
|
||||
} else {
|
||||
LogHelper.error(`The skill bridge is not supported: ${skillBridge}`)
|
||||
}
|
||||
} catch (e) {
|
||||
LogHelper.error(`Failed to save intent object: ${e}`)
|
||||
}
|
||||
@@ -299,8 +319,8 @@ export default class Brain {
|
||||
const executionTimeStart = Date.now()
|
||||
|
||||
return new Promise(async (resolve) => {
|
||||
const utteranceId = `${Date.now()}-${StringHelper.random(4)}`
|
||||
const intentObjectPath = path.join(TMP_PATH, `${utteranceId}.json`)
|
||||
const utteranceID = `${Date.now()}-${StringHelper.random(4)}`
|
||||
const intentObjectPath = path.join(TMP_PATH, `${utteranceID}.json`)
|
||||
const speeches: string[] = []
|
||||
|
||||
// Reset skill output
|
||||
@@ -334,20 +354,25 @@ export default class Brain {
|
||||
? actions[action.next_action]
|
||||
: null
|
||||
|
||||
if (actionType === SkillActionType.Logic) {
|
||||
if (actionType === SkillActionTypes.Logic) {
|
||||
/**
|
||||
* "Logic" action skill execution
|
||||
*/
|
||||
|
||||
this.executeLogicActionSkill(nluResult, utteranceId, intentObjectPath)
|
||||
|
||||
const domainName = nluResult.classification.domain
|
||||
const skillName = nluResult.classification.skill
|
||||
const { name: domainFriendlyName } =
|
||||
await SkillDomainHelper.getSkillDomainInfo(domainName)
|
||||
const { name: skillFriendlyName } =
|
||||
const { name: skillFriendlyName, bridge: skillBridge } =
|
||||
await SkillDomainHelper.getSkillInfo(domainName, skillName)
|
||||
|
||||
await this.executeLogicActionSkill(
|
||||
nluResult,
|
||||
skillBridge,
|
||||
utteranceID,
|
||||
intentObjectPath
|
||||
)
|
||||
|
||||
this.domainFriendlyName = domainFriendlyName
|
||||
this.skillFriendlyName = skillFriendlyName
|
||||
|
||||
@@ -383,7 +408,7 @@ export default class Brain {
|
||||
|
||||
// Synchronize the downloaded content if enabled
|
||||
if (
|
||||
skillResult.output.type === SkillOutputType.End &&
|
||||
skillResult.output.type === SkillOutputTypes.End &&
|
||||
skillResult.output.options['synchronization'] &&
|
||||
skillResult.output.options['synchronization'].enabled &&
|
||||
skillResult.output.options['synchronization'].enabled ===
|
||||
@@ -438,7 +463,7 @@ export default class Brain {
|
||||
}
|
||||
|
||||
resolve({
|
||||
utteranceId,
|
||||
utteranceID,
|
||||
lang: this._lang,
|
||||
...nluResult,
|
||||
speeches,
|
||||
@@ -567,7 +592,7 @@ export default class Brain {
|
||||
}
|
||||
|
||||
resolve({
|
||||
utteranceId,
|
||||
utteranceID,
|
||||
lang: this._lang,
|
||||
...nluResult,
|
||||
speeches: [answer as string],
|
||||
|
||||
@@ -28,7 +28,7 @@ export interface SkillResult {
|
||||
entities: NEREntity[]
|
||||
slots: NLUSlots
|
||||
output: {
|
||||
type: SkillOutputType
|
||||
type: SkillOutputTypes
|
||||
codes: string[]
|
||||
speech: string
|
||||
core: SkillCoreData | undefined
|
||||
@@ -37,11 +37,15 @@ export interface SkillResult {
|
||||
}
|
||||
}
|
||||
|
||||
export enum SkillOutputType {
|
||||
export enum SkillBridges {
|
||||
Python = 'python',
|
||||
NodeJS = 'nodejs'
|
||||
}
|
||||
export enum SkillOutputTypes {
|
||||
Intermediate = 'inter',
|
||||
End = 'end'
|
||||
}
|
||||
export enum SkillActionType {
|
||||
export enum SkillActionTypes {
|
||||
Logic = 'logic',
|
||||
Dialog = 'dialog'
|
||||
}
|
||||
@@ -63,7 +67,7 @@ export interface IntentObject {
|
||||
export interface BrainProcessResult extends NLUResult {
|
||||
speeches: string[]
|
||||
executionTime: number
|
||||
utteranceId?: string
|
||||
utteranceID?: string
|
||||
lang?: ShortLanguageCode
|
||||
core?: SkillCoreData | undefined
|
||||
action?: SkillConfigSchema['actions'][string]
|
||||
|
||||
@@ -1,13 +1,13 @@
|
||||
import type { FastifyPluginAsync } from 'fastify'
|
||||
|
||||
import { version } from '@@/package.json'
|
||||
import {
|
||||
LEON_VERSION,
|
||||
HAS_AFTER_SPEECH,
|
||||
HAS_LOGGER,
|
||||
HAS_STT,
|
||||
HAS_TTS,
|
||||
STT_PROVIDER,
|
||||
TTS_PROVIDER
|
||||
TTS_PROVIDER,
|
||||
IS_TELEMETRY_ENABLED
|
||||
} from '@/constants'
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
import type { APIOptions } from '@/core/http-server/http-server'
|
||||
@@ -30,7 +30,7 @@ export const getInfo: FastifyPluginAsync<APIOptions> = async (
|
||||
code: 'info_pulled',
|
||||
message,
|
||||
after_speech: HAS_AFTER_SPEECH,
|
||||
logger: HAS_LOGGER,
|
||||
telemetry: IS_TELEMETRY_ENABLED,
|
||||
stt: {
|
||||
enabled: HAS_STT,
|
||||
provider: STT_PROVIDER
|
||||
@@ -39,7 +39,7 @@ export const getInfo: FastifyPluginAsync<APIOptions> = async (
|
||||
enabled: HAS_TTS,
|
||||
provider: TTS_PROVIDER
|
||||
},
|
||||
version
|
||||
version: LEON_VERSION
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
@@ -5,8 +5,12 @@ import fastifyStatic from '@fastify/static'
|
||||
import { Type } from '@sinclair/typebox'
|
||||
import type { Static } from '@sinclair/typebox'
|
||||
|
||||
import { version } from '@@/package.json'
|
||||
import { LEON_NODE_ENV, HAS_LOGGER, HAS_OVER_HTTP } from '@/constants'
|
||||
import {
|
||||
LEON_VERSION,
|
||||
LEON_NODE_ENV,
|
||||
HAS_OVER_HTTP,
|
||||
IS_TELEMETRY_ENABLED
|
||||
} from '@/constants'
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
import { DateHelper } from '@/helpers/date-helper'
|
||||
import { corsMidd } from '@/core/http-server/plugins/cors'
|
||||
@@ -60,12 +64,12 @@ export default class HTTPServer {
|
||||
|
||||
LogHelper.title('Initialization')
|
||||
LogHelper.info(`The current env is ${LEON_NODE_ENV}`)
|
||||
LogHelper.info(`The current version is ${version}`)
|
||||
LogHelper.info(`The current version is ${LEON_VERSION}`)
|
||||
|
||||
LogHelper.info(`The current time zone is ${DateHelper.getTimeZone()}`)
|
||||
|
||||
const sLogger = !HAS_LOGGER ? 'disabled' : 'enabled'
|
||||
LogHelper.info(`Collaborative logger ${sLogger}`)
|
||||
const isTelemetryEnabled = IS_TELEMETRY_ENABLED ? 'enabled' : 'disabled'
|
||||
LogHelper.info(`Telemetry ${isTelemetryEnabled}`)
|
||||
|
||||
await this.bootstrap()
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import kill from 'tree-kill'
|
||||
|
||||
import type { Language, ShortLanguageCode } from '@/types'
|
||||
import type {
|
||||
NLUProcessResult,
|
||||
NLPAction,
|
||||
NLPDomain,
|
||||
NLPJSProcessResult,
|
||||
@@ -12,7 +13,6 @@ import type {
|
||||
NLPUtterance,
|
||||
NLUResult
|
||||
} from '@/core/nlp/types'
|
||||
import type { BrainProcessResult } from '@/core/brain/types'
|
||||
import { langs } from '@@/core/langs.json'
|
||||
import { TCP_SERVER_BIN_PATH } from '@/constants'
|
||||
import { TCP_CLIENT, BRAIN, SOCKET_SERVER, MODEL_LOADER, NER } from '@/core'
|
||||
@@ -21,13 +21,7 @@ import { LangHelper } from '@/helpers/lang-helper'
|
||||
import { ActionLoop } from '@/core/nlp/nlu/action-loop'
|
||||
import { SlotFilling } from '@/core/nlp/nlu/slot-filling'
|
||||
import Conversation, { DEFAULT_ACTIVE_CONTEXT } from '@/core/nlp/conversation'
|
||||
|
||||
type NLUProcessResult = Promise<Partial<
|
||||
BrainProcessResult & {
|
||||
processingTime: number
|
||||
nluProcessingTime: number
|
||||
}
|
||||
> | null>
|
||||
import { Telemetry } from '@/telemetry'
|
||||
|
||||
export const DEFAULT_NLU_RESULT = {
|
||||
utterance: '',
|
||||
@@ -91,7 +85,7 @@ export default class NLU {
|
||||
* pick-up the right classification
|
||||
* and extract entities
|
||||
*/
|
||||
public process(utterance: NLPUtterance): NLUProcessResult {
|
||||
public process(utterance: NLPUtterance): Promise<NLUProcessResult | null> {
|
||||
const processingTimeStart = Date.now()
|
||||
|
||||
return new Promise(async (resolve, reject) => {
|
||||
@@ -185,8 +179,6 @@ export default class NLU {
|
||||
return resolve(null)
|
||||
}
|
||||
|
||||
// this.sendLog()
|
||||
|
||||
if (intent === 'None') {
|
||||
const fallback = this.fallback(
|
||||
langs[LangHelper.getLongCode(locale)].fallbacks
|
||||
@@ -202,6 +194,8 @@ export default class NLU {
|
||||
const msg = 'Intent not found'
|
||||
LogHelper.warning(msg)
|
||||
|
||||
Telemetry.utterance({ utterance, lang: BRAIN.lang })
|
||||
|
||||
return resolve(null)
|
||||
}
|
||||
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import type { ShortLanguageCode } from '@/types'
|
||||
import type { BrainProcessResult } from '@/core/brain/types'
|
||||
|
||||
/**
|
||||
* NLP types
|
||||
@@ -9,6 +10,13 @@ export type NLPSkill = string
|
||||
export type NLPAction = string
|
||||
export type NLPUtterance = string
|
||||
|
||||
export type NLUProcessResult = Partial<
|
||||
BrainProcessResult & {
|
||||
processingTime: number
|
||||
nluProcessingTime: number
|
||||
}
|
||||
>
|
||||
|
||||
export interface NLPJSProcessResult {
|
||||
locale: ShortLanguageCode
|
||||
utterance: NLPUtterance
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
} from '@/core'
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
import { LangHelper } from '@/helpers/lang-helper'
|
||||
import { Telemetry } from '@/telemetry'
|
||||
|
||||
interface HotwordDataEvent {
|
||||
hotword: string
|
||||
@@ -115,7 +116,11 @@ export default class SocketServer {
|
||||
LogHelper.time('Utterance processed in')
|
||||
|
||||
BRAIN.isMuted = false
|
||||
await NLU.process(utterance)
|
||||
const processedData = await NLU.process(utterance)
|
||||
|
||||
if (processedData) {
|
||||
Telemetry.utterance(processedData)
|
||||
}
|
||||
|
||||
LogHelper.title('Execution Time')
|
||||
LogHelper.timeEnd('Utterance processed in')
|
||||
|
||||
@@ -4,7 +4,7 @@ import path from 'node:path'
|
||||
import { DateHelper } from '@/helpers/date-helper'
|
||||
|
||||
export class LogHelper {
|
||||
static readonly ERRORS_PATH = path.join(
|
||||
static readonly ERRORS_FILE_PATH = path.join(
|
||||
__dirname,
|
||||
'..',
|
||||
'..',
|
||||
@@ -47,10 +47,10 @@ export class LogHelper {
|
||||
public static error(value: string): void {
|
||||
const data = `${DateHelper.getDateTime()} - ${value}`
|
||||
|
||||
if (fs.existsSync(LogHelper.ERRORS_PATH)) {
|
||||
fs.appendFileSync(LogHelper.ERRORS_PATH, `\n${data}`)
|
||||
if (fs.existsSync(this.ERRORS_FILE_PATH)) {
|
||||
fs.appendFileSync(this.ERRORS_FILE_PATH, `\n${data}`)
|
||||
} else {
|
||||
fs.writeFileSync(LogHelper.ERRORS_PATH, data, { flag: 'wx' })
|
||||
fs.writeFileSync(this.ERRORS_FILE_PATH, data, { flag: 'wx' })
|
||||
}
|
||||
|
||||
console.error('\x1b[31m🚨 %s\x1b[0m', value)
|
||||
@@ -83,4 +83,31 @@ export class LogHelper {
|
||||
public static timeEnd(value: string): void {
|
||||
console.timeEnd(`🕑 \x1b[36m${value}\x1b[0m`)
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse error logs and return an array of log errors
|
||||
* @example parseErrorLogs() // 'Failed to connect to the TCP server: Error: read ECONNRESET'
|
||||
*/
|
||||
public static async parseErrorLogs(): Promise<string[]> {
|
||||
if (!fs.existsSync(LogHelper.ERRORS_FILE_PATH)) {
|
||||
const fileHandle = await fs.promises.open(LogHelper.ERRORS_FILE_PATH, 'w')
|
||||
|
||||
await fileHandle.close()
|
||||
}
|
||||
|
||||
const errorFileContent = await fs.promises.readFile(
|
||||
LogHelper.ERRORS_FILE_PATH,
|
||||
'utf8'
|
||||
)
|
||||
const errorLogs = errorFileContent
|
||||
.trim()
|
||||
.split(/\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}[+-]\d{2}:\d{2} - /)
|
||||
|
||||
// Remove the first empty string if there's one
|
||||
if (errorLogs[0] === '') {
|
||||
errorLogs.shift()
|
||||
}
|
||||
|
||||
return errorLogs
|
||||
}
|
||||
}
|
||||
|
||||
@@ -118,4 +118,33 @@ export class SystemHelper {
|
||||
public static getTotalRAM(): number {
|
||||
return Number((os.totalmem() / (1_024 * 1_024 * 1_024)).toFixed(2))
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the Node.js version of the current process
|
||||
* @example getNodeJSVersion() // '18.15.0'
|
||||
*/
|
||||
public static getNodeJSVersion(): string {
|
||||
return process.versions.node || '0.0.0'
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the npm version used to run the current process
|
||||
* @example getNPMVersion() // '9.5.0'
|
||||
*/
|
||||
public static getNPMVersion(): string {
|
||||
return (
|
||||
process.env['npm_config_user_agent']?.split('/')[1]?.split(' ')[0] ||
|
||||
'0.0.0'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Replace all current session profile name occurrences with {username} placeholder
|
||||
* @example sanitizeUsername('/home/louis') // '/home/{username}'
|
||||
*/
|
||||
public static sanitizeUsername(str: string): string {
|
||||
const { username } = os.userInfo()
|
||||
|
||||
return str.replace(new RegExp(username, 'g'), '{username}')
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,12 +1,16 @@
|
||||
import { spawn } from 'node:child_process'
|
||||
import fs from 'node:fs'
|
||||
|
||||
import {
|
||||
IS_DEVELOPMENT_ENV,
|
||||
IS_TELEMETRY_ENABLED,
|
||||
LANG as LEON_LANG,
|
||||
TCP_SERVER_BIN_PATH
|
||||
} from '@/constants'
|
||||
import { TCP_CLIENT, HTTP_SERVER, SOCKET_SERVER } from '@/core'
|
||||
import { Telemetry } from '@/telemetry'
|
||||
import { LangHelper } from '@/helpers/lang-helper'
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
;(async (): Promise<void> => {
|
||||
process.title = 'leon'
|
||||
|
||||
@@ -31,4 +35,39 @@ import { LangHelper } from '@/helpers/lang-helper'
|
||||
|
||||
// Start the socket server
|
||||
SOCKET_SERVER.init()
|
||||
|
||||
// Telemetry events
|
||||
if (IS_TELEMETRY_ENABLED) {
|
||||
Telemetry.start()
|
||||
|
||||
// Watch for errors in the error log file and report them to the telemetry service
|
||||
fs.watchFile(LogHelper.ERRORS_FILE_PATH, async () => {
|
||||
const logErrors = await LogHelper.parseErrorLogs()
|
||||
const lastError = logErrors[logErrors.length - 1] || ''
|
||||
|
||||
Telemetry.error(lastError)
|
||||
})
|
||||
|
||||
setInterval(() => {
|
||||
Telemetry.heartbeat()
|
||||
}, 1_000 * 3_600)
|
||||
;[
|
||||
'exit',
|
||||
'SIGINT',
|
||||
'SIGUSR1',
|
||||
'SIGUSR2',
|
||||
'uncaughtException',
|
||||
'SIGTERM'
|
||||
].forEach((eventType) => {
|
||||
process.on(eventType, () => {
|
||||
Telemetry.stop()
|
||||
|
||||
global.tcpServerProcess.kill()
|
||||
|
||||
setTimeout(() => {
|
||||
process.exit(0)
|
||||
}, 1_000)
|
||||
})
|
||||
})
|
||||
}
|
||||
})()
|
||||
|
||||
+46
-3
@@ -29,21 +29,37 @@ import {
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
import { LangHelper } from '@/helpers/lang-helper'
|
||||
import { SkillDomainHelper } from '@/helpers/skill-domain-helper'
|
||||
import { VOICE_CONFIG_PATH, GLOBAL_DATA_PATH } from '@/constants'
|
||||
import {
|
||||
MINIMUM_REQUIRED_RAM,
|
||||
VOICE_CONFIG_PATH,
|
||||
GLOBAL_DATA_PATH
|
||||
} from '@/constants'
|
||||
import { getGlobalEntitiesPath, getGlobalResolversPath } from '@/utilities'
|
||||
import { SystemHelper } from '@/helpers/system-helper'
|
||||
|
||||
interface ObjectUnknown {
|
||||
[key: string]: unknown
|
||||
}
|
||||
|
||||
const validateSchema = (
|
||||
schemaName: string,
|
||||
schema: ObjectUnknown,
|
||||
contentToValidate: ObjectUnknown,
|
||||
customErrorMessage: string
|
||||
): void => {
|
||||
const schemaFile = `${schemaName}.json`
|
||||
const validate = ajv.compile(schema)
|
||||
const isValid = validate(contentToValidate)
|
||||
const isValidSchemaKey =
|
||||
typeof contentToValidate['$schema'] === 'string' &&
|
||||
contentToValidate['$schema'].endsWith(schemaFile)
|
||||
const isValid = validate(contentToValidate) && isValidSchemaKey
|
||||
if (!isValid) {
|
||||
LogHelper.error(customErrorMessage)
|
||||
if (!isValidSchemaKey) {
|
||||
LogHelper.error(
|
||||
`The schema key "$schema" is not valid. Expected "${schemaName}", but got "${contentToValidate['$schema']}".`
|
||||
)
|
||||
}
|
||||
LogHelper.error(customErrorMessage)
|
||||
const errors = new AggregateAjvError(validate.errors ?? [])
|
||||
for (const error of errors) {
|
||||
@@ -55,8 +71,11 @@ const validateSchema = (
|
||||
|
||||
/**
|
||||
* Pre-checking
|
||||
* Ensure JSON files are correctly formatted
|
||||
*
|
||||
* - Ensure the system requirements are met
|
||||
* - Ensure JSON files are correctly formatted
|
||||
*/
|
||||
|
||||
const VOICE_CONFIG_SCHEMAS = {
|
||||
amazon: amazonVoiceConfiguration,
|
||||
'google-cloud': googleCloudVoiceConfiguration,
|
||||
@@ -72,6 +91,23 @@ const GLOBAL_DATA_SCHEMAS = {
|
||||
;(async (): Promise<void> => {
|
||||
LogHelper.title('Pre-checking')
|
||||
|
||||
/**
|
||||
* System requirements checking
|
||||
*/
|
||||
LogHelper.info('Checking system requirements...')
|
||||
|
||||
const totalRAMInGB = Math.round(SystemHelper.getTotalRAM())
|
||||
|
||||
if (totalRAMInGB < MINIMUM_REQUIRED_RAM) {
|
||||
LogHelper.warning(
|
||||
`Total RAM: ${totalRAMInGB} GB. Leon needs at least ${MINIMUM_REQUIRED_RAM} GB of RAM. It may not work as expected.`
|
||||
)
|
||||
} else {
|
||||
LogHelper.success(
|
||||
`Minimum required RAM: ${MINIMUM_REQUIRED_RAM} GB | Total RAM: ${totalRAMInGB} GB`
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Voice configuration checking
|
||||
*/
|
||||
@@ -88,6 +124,7 @@ const GLOBAL_DATA_SCHEMAS = {
|
||||
)
|
||||
const [configName] = file.split('.') as [keyof typeof VOICE_CONFIG_SCHEMAS]
|
||||
validateSchema(
|
||||
`voice-config-schemas/${configName}`,
|
||||
VOICE_CONFIG_SCHEMAS[configName],
|
||||
config,
|
||||
`The voice configuration schema "${voiceConfigPath}" is not valid:`
|
||||
@@ -116,6 +153,7 @@ const GLOBAL_DATA_SCHEMAS = {
|
||||
await fs.promises.readFile(globalEntityPath, 'utf8')
|
||||
)
|
||||
validateSchema(
|
||||
'global-data/global-entity',
|
||||
globalEntitySchemaObject,
|
||||
globalEntity,
|
||||
`The global entity schema "${globalEntityPath}" is not valid:`
|
||||
@@ -136,6 +174,7 @@ const GLOBAL_DATA_SCHEMAS = {
|
||||
await fs.promises.readFile(globalResolverPath, 'utf8')
|
||||
)
|
||||
validateSchema(
|
||||
'global-data/global-resolver',
|
||||
globalResolverSchemaObject,
|
||||
globalResolver,
|
||||
`The global resolver schema "${globalResolverPath}" is not valid:`
|
||||
@@ -150,6 +189,7 @@ const GLOBAL_DATA_SCHEMAS = {
|
||||
await fs.promises.readFile(globalAnswersPath, 'utf8')
|
||||
)
|
||||
validateSchema(
|
||||
'global-data/global-answers',
|
||||
GLOBAL_DATA_SCHEMAS.answers,
|
||||
answers,
|
||||
`The global answers schema "${globalAnswersPath}" is not valid:`
|
||||
@@ -173,6 +213,7 @@ const GLOBAL_DATA_SCHEMAS = {
|
||||
await fs.promises.readFile(pathToDomain, 'utf8')
|
||||
)
|
||||
validateSchema(
|
||||
'skill-schemas/domain',
|
||||
domainSchemaObject,
|
||||
domainObject,
|
||||
`The domain schema "${pathToDomain}" is not valid:`
|
||||
@@ -192,6 +233,7 @@ const GLOBAL_DATA_SCHEMAS = {
|
||||
await fs.promises.readFile(pathToSkill, 'utf8')
|
||||
)
|
||||
validateSchema(
|
||||
'skill-schemas/skill',
|
||||
skillSchemaObject,
|
||||
skillObject,
|
||||
`The skill schema "${pathToSkill}" is not valid:`
|
||||
@@ -211,6 +253,7 @@ const GLOBAL_DATA_SCHEMAS = {
|
||||
await fs.promises.readFile(skillConfigPath, 'utf8')
|
||||
)
|
||||
validateSchema(
|
||||
'skill-schemas/skill-config',
|
||||
skillConfigSchemaObject,
|
||||
skillConfig,
|
||||
`The skill config schema "${skillConfigPath}" is not valid:`
|
||||
|
||||
@@ -17,40 +17,37 @@ export const globalEntitySchemaObject = Type.Strict(
|
||||
)
|
||||
)
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
{
|
||||
description:
|
||||
'Global entities can hold data that can directly be reused in skills.'
|
||||
}
|
||||
)
|
||||
)
|
||||
export const globalResolverSchemaObject = Type.Strict(
|
||||
Type.Object(
|
||||
{
|
||||
name: Type.String(),
|
||||
intents: Type.Record(
|
||||
Type.String(),
|
||||
Type.Object(
|
||||
{
|
||||
utterance_samples: Type.Array(Type.String()),
|
||||
value: Type.Unknown()
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
Type.Object({
|
||||
name: Type.String(),
|
||||
intents: Type.Record(
|
||||
Type.String(),
|
||||
Type.Object(
|
||||
{
|
||||
utterance_samples: Type.Array(Type.String()),
|
||||
value: Type.Unknown()
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
)
|
||||
})
|
||||
)
|
||||
export const globalAnswersSchemaObject = Type.Strict(
|
||||
Type.Object(
|
||||
{
|
||||
answers: Type.Record(
|
||||
Type.String(),
|
||||
Type.Union([
|
||||
Type.Record(Type.String(), Type.String()),
|
||||
Type.Array(Type.String())
|
||||
])
|
||||
)
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
Type.Object({
|
||||
answers: Type.Record(
|
||||
Type.String(),
|
||||
Type.Union([
|
||||
Type.Record(Type.String(), Type.String()),
|
||||
Type.Array(Type.String())
|
||||
])
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
export type GlobalEntitySchema = Static<typeof globalEntitySchemaObject>
|
||||
|
||||
+173
-104
@@ -2,9 +2,22 @@ import type { Static } from '@sinclair/typebox'
|
||||
import { Type } from '@sinclair/typebox'
|
||||
|
||||
import { globalResolverSchemaObject } from '@/schemas/global-data-schemas'
|
||||
import { SkillBridges } from '@/core/brain/types'
|
||||
|
||||
const skillBridges = [Type.Literal('python'), Type.Null()]
|
||||
const skillActionTypes = [Type.Literal('logic'), Type.Literal('dialog')]
|
||||
const skillBridges = [
|
||||
Type.Literal(SkillBridges.Python),
|
||||
Type.Literal(SkillBridges.NodeJS),
|
||||
Type.Null()
|
||||
]
|
||||
const skillActionTypes = [
|
||||
Type.Literal('logic', {
|
||||
description: 'It runs the business logic implemented in actions via code.'
|
||||
}),
|
||||
Type.Literal('dialog', {
|
||||
description:
|
||||
"Action that don't need code to run. Leon actually just answers without any business logic."
|
||||
})
|
||||
]
|
||||
const skillDataTypes = [
|
||||
Type.Literal('skill_resolver'),
|
||||
Type.Literal('global_resolver'),
|
||||
@@ -21,7 +34,11 @@ const skillCustomEnumEntityType = Type.Object(
|
||||
})
|
||||
)
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
{
|
||||
additionalProperties: false,
|
||||
description:
|
||||
'Enum: define a bag of words and synonyms that should match your new entity.'
|
||||
}
|
||||
)
|
||||
const skillCustomRegexEntityType = Type.Object(
|
||||
{
|
||||
@@ -29,7 +46,10 @@ const skillCustomRegexEntityType = Type.Object(
|
||||
name: Type.String({ minLength: 1 }),
|
||||
regex: Type.String({ minLength: 1 })
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
{
|
||||
additionalProperties: false,
|
||||
description: 'Regex: you can create an entity based on a regex.'
|
||||
}
|
||||
)
|
||||
const skillCustomTrimEntityType = Type.Object(
|
||||
{
|
||||
@@ -60,7 +80,11 @@ const skillCustomTrimEntityType = Type.Object(
|
||||
])
|
||||
)
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
{
|
||||
additionalProperties: false,
|
||||
description:
|
||||
'Trim: you can pick up a data from an utterance by clearly defining conditions (e.g: pick up what is after the last "with" word of the utterance).'
|
||||
}
|
||||
)
|
||||
)
|
||||
},
|
||||
@@ -73,119 +97,164 @@ const skillCustomEntityTypes = [
|
||||
]
|
||||
|
||||
export const domainSchemaObject = Type.Strict(
|
||||
Type.Object(
|
||||
{
|
||||
name: Type.String({ minLength: 1 })
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
Type.Object({
|
||||
name: Type.String({ minLength: 1, description: 'The name of the domain.' })
|
||||
})
|
||||
)
|
||||
export const skillSchemaObject = Type.Strict(
|
||||
Type.Object(
|
||||
{
|
||||
name: Type.String({ minLength: 1 }),
|
||||
bridge: Type.Union(skillBridges),
|
||||
version: Type.String({ minLength: 1 }),
|
||||
description: Type.String({ minLength: 1 }),
|
||||
author: Type.Object(
|
||||
Type.Object({
|
||||
name: Type.String({ minLength: 1, description: 'The name of the skill.' }),
|
||||
bridge: Type.Union(skillBridges, { description: 'Bridge SDK.' }),
|
||||
version: Type.String({
|
||||
minLength: 1,
|
||||
description: 'Version following semver.'
|
||||
}),
|
||||
description: Type.String({
|
||||
minLength: 1,
|
||||
description: 'This helps people understand what your skill does.'
|
||||
}),
|
||||
author: Type.Object(
|
||||
{
|
||||
name: Type.String({ minLength: 1, description: 'Name of the author.' }),
|
||||
email: Type.Optional(
|
||||
Type.String({
|
||||
minLength: 1,
|
||||
maxLength: 254,
|
||||
format: 'email',
|
||||
description: 'Email address of the author.'
|
||||
})
|
||||
),
|
||||
url: Type.Optional(
|
||||
Type.String({
|
||||
minLength: 1,
|
||||
maxLength: 255,
|
||||
format: 'uri',
|
||||
description: 'Website of the author.'
|
||||
})
|
||||
)
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
description:
|
||||
'A person who has been involved in creating or maintaining this skill.'
|
||||
}
|
||||
)
|
||||
})
|
||||
)
|
||||
export const skillConfigSchemaObject = Type.Strict(
|
||||
Type.Object({
|
||||
variables: Type.Optional(Type.Record(Type.String(), Type.String())),
|
||||
actions: Type.Record(
|
||||
Type.String(),
|
||||
Type.Object(
|
||||
{
|
||||
name: Type.String({ minLength: 1 }),
|
||||
email: Type.Optional(
|
||||
Type.String({ minLength: 1, maxLength: 254, format: 'email' })
|
||||
type: Type.Union(skillActionTypes),
|
||||
loop: Type.Optional(
|
||||
Type.Object(
|
||||
{
|
||||
expected_item: Type.Object(
|
||||
{
|
||||
type: Type.Union(skillDataTypes),
|
||||
name: Type.String()
|
||||
},
|
||||
{ description: 'An item can be a entity or a resolver.' }
|
||||
)
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
description:
|
||||
'The action loop is a concept to keep Leon triggering the same skill action until the logic of the skill breaks the loop according to new utterances content.'
|
||||
}
|
||||
)
|
||||
),
|
||||
url: Type.Optional(
|
||||
Type.String({ minLength: 1, maxLength: 255, format: 'uri' })
|
||||
http_api: Type.Optional(
|
||||
Type.Object(
|
||||
{
|
||||
entities: Type.Array(
|
||||
Type.Object(
|
||||
{
|
||||
entity: Type.String(),
|
||||
resolution: Type.Array(Type.String())
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
)
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
),
|
||||
utterance_samples: Type.Optional(Type.Array(Type.String())),
|
||||
answers: Type.Optional(Type.Array(Type.String())),
|
||||
unknown_answers: Type.Optional(Type.Array(Type.String())),
|
||||
suggestions: Type.Optional(
|
||||
Type.Array(Type.String(), {
|
||||
description:
|
||||
'Suggestions are a simple way to suggest Leon owners what can be answered next.'
|
||||
})
|
||||
),
|
||||
slots: Type.Optional(
|
||||
Type.Array(
|
||||
Type.Object(
|
||||
{
|
||||
name: Type.String(),
|
||||
item: Type.Object(
|
||||
{
|
||||
type: Type.Union(skillDataTypes),
|
||||
name: Type.String()
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
),
|
||||
questions: Type.Array(Type.String()),
|
||||
suggestions: Type.Optional(
|
||||
Type.Array(Type.String(), {
|
||||
description:
|
||||
'Suggestions are a simple way to suggest Leon owners what can be answered next.'
|
||||
})
|
||||
)
|
||||
},
|
||||
{
|
||||
additionalProperties: false,
|
||||
description:
|
||||
'A slot expects a type of data called "item", and makes use of questions to let Leon owners knows what data they need to provide.'
|
||||
}
|
||||
),
|
||||
{
|
||||
description:
|
||||
'Depending on how skill developers wants to design their skill, they have the possibility to ask for more information before to get to the meat of the skill. In this way, Leon can gather these information to operate the skill in a complete manner. These information are called "slots".'
|
||||
}
|
||||
)
|
||||
),
|
||||
entities: Type.Optional(Type.Union(skillCustomEntityTypes)),
|
||||
next_action: Type.Optional(
|
||||
Type.String({
|
||||
description:
|
||||
'The next action property is useful when a skill needs to follow a specific order of actions, it helps to connect actions in a specific order to feed the context with data.'
|
||||
})
|
||||
)
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
)
|
||||
export const skillConfigSchemaObject = Type.Strict(
|
||||
Type.Object(
|
||||
{
|
||||
variables: Type.Optional(Type.Record(Type.String(), Type.String())),
|
||||
actions: Type.Record(
|
||||
),
|
||||
answers: Type.Optional(
|
||||
Type.Record(Type.String(), Type.Array(Type.String()))
|
||||
),
|
||||
entities: Type.Optional(Type.Record(Type.String(), Type.String())),
|
||||
resolvers: Type.Optional(
|
||||
Type.Record(
|
||||
Type.String(),
|
||||
Type.Object(
|
||||
{
|
||||
type: Type.Union(skillActionTypes),
|
||||
loop: Type.Optional(
|
||||
Type.Object(
|
||||
{
|
||||
expected_item: Type.Object({
|
||||
type: Type.Union(skillDataTypes),
|
||||
name: Type.String()
|
||||
})
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
),
|
||||
http_api: Type.Optional(
|
||||
Type.Object(
|
||||
{
|
||||
entities: Type.Array(
|
||||
Type.Object(
|
||||
{
|
||||
entity: Type.String(),
|
||||
resolution: Type.Array(Type.String())
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
)
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
),
|
||||
utterance_samples: Type.Optional(Type.Array(Type.String())),
|
||||
answers: Type.Optional(Type.Array(Type.String())),
|
||||
unknown_answers: Type.Optional(Type.Array(Type.String())),
|
||||
suggestions: Type.Optional(Type.Array(Type.String())),
|
||||
slots: Type.Optional(
|
||||
Type.Array(
|
||||
Type.Object(
|
||||
{
|
||||
name: Type.String(),
|
||||
item: Type.Object(
|
||||
{
|
||||
type: Type.Union(skillDataTypes),
|
||||
name: Type.String()
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
),
|
||||
questions: Type.Array(Type.String()),
|
||||
suggestions: Type.Optional(Type.Array(Type.String()))
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
)
|
||||
),
|
||||
entities: Type.Optional(Type.Union(skillCustomEntityTypes)),
|
||||
next_action: Type.Optional(Type.String())
|
||||
intents: globalResolverSchemaObject.properties.intents
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
),
|
||||
answers: Type.Optional(
|
||||
Type.Record(Type.String(), Type.Array(Type.String()))
|
||||
),
|
||||
entities: Type.Optional(Type.Record(Type.String(), Type.String())),
|
||||
resolvers: Type.Optional(
|
||||
Type.Record(
|
||||
Type.String(),
|
||||
Type.Object(
|
||||
{
|
||||
intents: globalResolverSchemaObject.properties.intents
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
)
|
||||
),
|
||||
{
|
||||
description:
|
||||
'You can see resolvers as utterance samples that are converted (resolved) to a value of your choice. They are very handy when skills expect specific utterances and then according to these utterances attribute a value that can be handled by the skill. If a skill action expects to receive a resolver, then Leon will convert the value for you and this value will be usable from the skill action code. Any value can be passed to resolvers which allow a large possibilities of usages.'
|
||||
}
|
||||
)
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
)
|
||||
})
|
||||
)
|
||||
|
||||
export type DomainSchema = Static<typeof domainSchemaObject>
|
||||
|
||||
@@ -2,42 +2,33 @@ import type { Static } from '@sinclair/typebox'
|
||||
import { Type } from '@sinclair/typebox'
|
||||
|
||||
export const amazonVoiceConfiguration = Type.Strict(
|
||||
Type.Object(
|
||||
{
|
||||
credentials: Type.Object({
|
||||
accessKeyId: Type.String(),
|
||||
secretAccessKey: Type.String()
|
||||
}),
|
||||
region: Type.String()
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
Type.Object({
|
||||
credentials: Type.Object({
|
||||
accessKeyId: Type.String(),
|
||||
secretAccessKey: Type.String()
|
||||
}),
|
||||
region: Type.String()
|
||||
})
|
||||
)
|
||||
export const googleCloudVoiceConfiguration = Type.Strict(
|
||||
Type.Object(
|
||||
{
|
||||
type: Type.Literal('service_account'),
|
||||
project_id: Type.String(),
|
||||
private_key_id: Type.String(),
|
||||
private_key: Type.String(),
|
||||
client_email: Type.String({ format: 'email' }),
|
||||
client_id: Type.String(),
|
||||
auth_uri: Type.String({ format: 'uri' }),
|
||||
token_uri: Type.String({ format: 'uri' }),
|
||||
auth_provider_x509_cert_url: Type.String({ format: 'uri' }),
|
||||
client_x509_cert_url: Type.String({ format: 'uri' })
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
Type.Object({
|
||||
type: Type.Literal('service_account'),
|
||||
project_id: Type.String(),
|
||||
private_key_id: Type.String(),
|
||||
private_key: Type.String(),
|
||||
client_email: Type.String({ format: 'email' }),
|
||||
client_id: Type.String(),
|
||||
auth_uri: Type.String({ format: 'uri' }),
|
||||
token_uri: Type.String({ format: 'uri' }),
|
||||
auth_provider_x509_cert_url: Type.String({ format: 'uri' }),
|
||||
client_x509_cert_url: Type.String({ format: 'uri' })
|
||||
})
|
||||
)
|
||||
export const watsonVoiceConfiguration = Type.Strict(
|
||||
Type.Object(
|
||||
{
|
||||
apikey: Type.String(),
|
||||
url: Type.String({ format: 'uri' })
|
||||
},
|
||||
{ additionalProperties: false }
|
||||
)
|
||||
Type.Object({
|
||||
apikey: Type.String(),
|
||||
url: Type.String({ format: 'uri' })
|
||||
})
|
||||
)
|
||||
|
||||
export type AmazonVoiceConfigurationSchema = Static<
|
||||
|
||||
@@ -0,0 +1,263 @@
|
||||
import os from 'node:os'
|
||||
|
||||
import type { Os } from 'getos'
|
||||
import axios from 'axios'
|
||||
import osName from 'os-name'
|
||||
import getos from 'getos'
|
||||
|
||||
import type {
|
||||
NEREntity,
|
||||
NLPUtterance,
|
||||
NLUProcessResult,
|
||||
NLUResult
|
||||
} from '@/core/nlp/types'
|
||||
import {
|
||||
IS_TELEMETRY_ENABLED,
|
||||
INSTANCE_ID,
|
||||
IS_GITPOD,
|
||||
IS_DEVELOPMENT_ENV,
|
||||
IS_PRODUCTION_ENV,
|
||||
LANG,
|
||||
LEON_VERSION,
|
||||
NODEJS_BRIDGE_VERSION,
|
||||
PYTHON_BRIDGE_VERSION,
|
||||
STT_PROVIDER,
|
||||
TCP_SERVER_VERSION,
|
||||
TTS_PROVIDER
|
||||
} from '@/constants'
|
||||
import { SystemHelper } from '@/helpers/system-helper'
|
||||
import { SkillDomainHelper } from '@/helpers/skill-domain-helper'
|
||||
import { LogHelper } from '@/helpers/log-helper'
|
||||
|
||||
interface PostIntallResponse {
|
||||
instanceID: string
|
||||
birthDate: number
|
||||
}
|
||||
|
||||
enum EventNames {
|
||||
Heartbeat = 'HEARTBEAT',
|
||||
Stopped = 'STOPPED'
|
||||
}
|
||||
|
||||
export class Telemetry {
|
||||
private static readonly serviceURL = 'https://telemetry.getleon.ai'
|
||||
// private static readonly serviceURL = 'http://localhost:3000'
|
||||
private static readonly instanceID = INSTANCE_ID
|
||||
private static readonly axios = axios.create({
|
||||
baseURL: this.serviceURL,
|
||||
timeout: 7_000
|
||||
})
|
||||
|
||||
public static async postInstall(): Promise<PostIntallResponse | unknown> {
|
||||
try {
|
||||
const { data } = await this.axios.post('/on-post-install', {
|
||||
instanceID: this.instanceID,
|
||||
isGitpod: IS_GITPOD
|
||||
})
|
||||
|
||||
return data
|
||||
} catch (e) {
|
||||
return {}
|
||||
}
|
||||
}
|
||||
|
||||
public static async start(): Promise<void> {
|
||||
if (IS_TELEMETRY_ENABLED) {
|
||||
try {
|
||||
const platform = os.platform()
|
||||
const data = {
|
||||
isProduction: IS_PRODUCTION_ENV,
|
||||
isGitpod: IS_GITPOD,
|
||||
isOnline: true,
|
||||
language: LANG,
|
||||
sttProvider: STT_PROVIDER,
|
||||
ttsProvider: TTS_PROVIDER,
|
||||
coreVersion: LEON_VERSION,
|
||||
nodeJSBridgeVersion: NODEJS_BRIDGE_VERSION,
|
||||
pythonBridgeVersion: PYTHON_BRIDGE_VERSION,
|
||||
tcpServerVersion: TCP_SERVER_VERSION,
|
||||
environment: {
|
||||
osDetails: {
|
||||
type: os.type(),
|
||||
platform,
|
||||
arch: os.arch(),
|
||||
cpus: os.cpus().length,
|
||||
release: os.release(),
|
||||
osName: osName(),
|
||||
distro: null as Os | null
|
||||
},
|
||||
totalRAMInGB: SystemHelper.getTotalRAM(),
|
||||
nodeVersion: SystemHelper.getNodeJSVersion(),
|
||||
npmVersion: SystemHelper.getNPMVersion()
|
||||
}
|
||||
}
|
||||
|
||||
if (platform === 'linux') {
|
||||
getos(async (e, os) => {
|
||||
if (e) {
|
||||
/* */
|
||||
}
|
||||
data.environment.osDetails.distro = os
|
||||
|
||||
try {
|
||||
await this.axios.post('/on-start', {
|
||||
instanceID: this.instanceID,
|
||||
data
|
||||
})
|
||||
} catch (e) {
|
||||
if (IS_DEVELOPMENT_ENV) {
|
||||
LogHelper.title('Telemetry')
|
||||
LogHelper.warning(
|
||||
`Failed to send start data to telemetry service: ${e}`
|
||||
)
|
||||
}
|
||||
}
|
||||
})
|
||||
} else {
|
||||
await this.axios.post('/on-start', {
|
||||
instanceID: this.instanceID,
|
||||
data
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
if (IS_DEVELOPMENT_ENV) {
|
||||
LogHelper.title('Telemetry')
|
||||
LogHelper.warning(
|
||||
`Failed to send start data to telemetry service: ${e}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async utterance(
|
||||
processedData: NLUProcessResult | null
|
||||
): Promise<void> {
|
||||
if (IS_TELEMETRY_ENABLED) {
|
||||
try {
|
||||
if (processedData?.classification) {
|
||||
const {
|
||||
classification: {
|
||||
domain: triggeredDomain,
|
||||
skill: triggeredSkill,
|
||||
action: triggeredAction,
|
||||
confidence: probability
|
||||
},
|
||||
utterance,
|
||||
entities
|
||||
} = processedData as NLUResult
|
||||
const skill = await SkillDomainHelper.getSkillInfo(
|
||||
triggeredDomain,
|
||||
triggeredSkill
|
||||
)
|
||||
|
||||
await this.axios.post('/on-utterance', {
|
||||
instanceID: this.instanceID,
|
||||
data: {
|
||||
triggeredDomain: triggeredDomain || null,
|
||||
triggeredSkill: triggeredSkill || null,
|
||||
triggeredAction: triggeredAction || null,
|
||||
probability,
|
||||
language: processedData?.lang || null,
|
||||
processingTime: processedData?.processingTime || 0,
|
||||
executionTime: processedData?.executionTime || 0,
|
||||
nluProcessingTime: processedData?.nluProcessingTime || 0,
|
||||
value: this.anonymizeEntities(utterance, entities) || utterance,
|
||||
triggeredSkillVersion: skill.version || null,
|
||||
triggeredSkillBridge: skill.bridge || null
|
||||
}
|
||||
})
|
||||
} else if (JSON.stringify(processedData) !== JSON.stringify({})) {
|
||||
// Not in a skill loop
|
||||
await this.axios.post('/on-utterance', {
|
||||
instanceID: this.instanceID,
|
||||
data: {
|
||||
language: processedData?.lang || null,
|
||||
value: processedData?.utterance
|
||||
}
|
||||
})
|
||||
}
|
||||
} catch (e) {
|
||||
if (IS_DEVELOPMENT_ENV) {
|
||||
LogHelper.title('Telemetry')
|
||||
LogHelper.warning(
|
||||
`Failed to send utterance data to telemetry service: ${e}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async error(error: string): Promise<void> {
|
||||
if (IS_TELEMETRY_ENABLED) {
|
||||
try {
|
||||
await this.axios.post('/on-error', {
|
||||
instanceID: this.instanceID,
|
||||
error: SystemHelper.sanitizeUsername(error)
|
||||
})
|
||||
} catch (e) {
|
||||
if (IS_DEVELOPMENT_ENV) {
|
||||
LogHelper.title('Telemetry')
|
||||
LogHelper.warning(`Failed to send error to telemetry service: ${e}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async stop(): Promise<void> {
|
||||
if (IS_TELEMETRY_ENABLED) {
|
||||
try {
|
||||
await this.sendEvent(EventNames.Stopped)
|
||||
} catch (e) {
|
||||
if (IS_DEVELOPMENT_ENV) {
|
||||
LogHelper.title('Telemetry')
|
||||
LogHelper.warning(
|
||||
`Failed to send stop event to telemetry service: ${e}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static async heartbeat(): Promise<void> {
|
||||
if (IS_TELEMETRY_ENABLED) {
|
||||
try {
|
||||
await this.sendEvent(EventNames.Heartbeat)
|
||||
} catch (e) {
|
||||
if (IS_DEVELOPMENT_ENV) {
|
||||
LogHelper.title('Telemetry')
|
||||
LogHelper.warning(
|
||||
`Failed to send heartbeat event to telemetry service: ${e}`
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static async sendEvent(eventName: EventNames): Promise<void> {
|
||||
if (IS_TELEMETRY_ENABLED) {
|
||||
try {
|
||||
await this.axios.post('/on-event', {
|
||||
instanceID: this.instanceID,
|
||||
eventName
|
||||
})
|
||||
} catch (e) {
|
||||
if (IS_DEVELOPMENT_ENV) {
|
||||
LogHelper.title('Telemetry')
|
||||
LogHelper.warning(`Failed to send event to telemetry service: ${e}`)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static anonymizeEntities(
|
||||
utterance: NLPUtterance,
|
||||
entities: NEREntity[]
|
||||
): NLPUtterance {
|
||||
entities.forEach((entity) => {
|
||||
utterance = utterance.replace(entity.sourceText, `{${entity.entity}}`)
|
||||
})
|
||||
|
||||
return utterance
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"$schema": "../../schemas/skill-schemas/domain.json",
|
||||
"name": "Business & Finance"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"$schema": "../../schemas/skill-schemas/domain.json",
|
||||
"name": "Food & Drink"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"choose_thematic": {
|
||||
"type": "dialog",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/skill-schemas/skill.json",
|
||||
"name": "Akinator",
|
||||
"bridge": "python",
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"$schema": "../../schemas/skill-schemas/domain.json",
|
||||
"name": "Games"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"setup": {
|
||||
"type": "logic",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/skill-schemas/skill.json",
|
||||
"name": "Guess the Number",
|
||||
"bridge": "python",
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"start": {
|
||||
"type": "dialog",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/skill-schemas/skill.json",
|
||||
"name": "Rochambeau",
|
||||
"bridge": "python",
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"$schema": "../../schemas/skill-schemas/domain.json",
|
||||
"name": "Health & Fitness"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"$schema": "../../schemas/skill-schemas/domain.json",
|
||||
"name": "Knowledge & Education"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"variables": {
|
||||
"blue_leon": "#1C75DB",
|
||||
"pink_leon": "#ED297A"
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/skill-schemas/skill.json",
|
||||
"name": "Color",
|
||||
"bridge": null,
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"$schema": "../../schemas/skill-schemas/domain.json",
|
||||
"name": "Leon"
|
||||
}
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"run": {
|
||||
"type": "dialog",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"run": {
|
||||
"type": "dialog",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/skill-schemas/skill.json",
|
||||
"name": "Good Bye",
|
||||
"bridge": null,
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"run": {
|
||||
"type": "logic",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"run": {
|
||||
"type": "logic",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/skill-schemas/skill.json",
|
||||
"name": "Greeting",
|
||||
"bridge": "python",
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"variables": {
|
||||
"leon_introduction_1": "I'm your daily personal assistant. I have been created by Louis. I'm very happy to serve you everyday.",
|
||||
"leon_introduction_2": "The question is, who are you? I'm kidding! I'm your daily personal assistant. Louis created me to make your life easier.",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"run": {
|
||||
"type": "logic",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/skill-schemas/skill.json",
|
||||
"name": "Introduction",
|
||||
"bridge": "python",
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"run": {
|
||||
"type": "dialog",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"run": {
|
||||
"type": "dialog",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/skill-schemas/skill.json",
|
||||
"name": "Joke",
|
||||
"bridge": null,
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"run": {
|
||||
"type": "dialog",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"run": {
|
||||
"type": "dialog",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/skill-schemas/skill.json",
|
||||
"name": "Meaning of Life",
|
||||
"bridge": null,
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"run": {
|
||||
"type": "dialog",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"run": {
|
||||
"type": "dialog",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/skill-schemas/skill.json",
|
||||
"name": "Partner Assistant",
|
||||
"bridge": null,
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"run": {
|
||||
"type": "logic",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"run": {
|
||||
"type": "logic",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/skill-schemas/skill.json",
|
||||
"name": "Random Number",
|
||||
"bridge": "python",
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"run": {
|
||||
"type": "dialog",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||
"actions": {
|
||||
"run": {
|
||||
"type": "dialog",
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
{
|
||||
"$schema": "../../../schemas/skill-schemas/skill.json",
|
||||
"name": "Welcome",
|
||||
"bridge": null,
|
||||
"version": "1.0.0",
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"$schema": "../../schemas/skill-schemas/domain.json",
|
||||
"name": "Movies & TV"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"$schema": "../../schemas/skill-schemas/domain.json",
|
||||
"name": "Music & Audio"
|
||||
}
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
{
|
||||
"$schema": "../../schemas/skill-schemas/domain.json",
|
||||
"name": "News"
|
||||
}
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user