mirror of
https://github.com/leon-ai/leon.git
synced 2024-12-24 09:12:20 +03:00
Merge branch 'nodejs-bridge' into develop
This commit is contained in:
commit
2c2f49290a
@ -11,5 +11,8 @@
|
|||||||
"homepage": "https://getleon.ai",
|
"homepage": "https://getleon.ai",
|
||||||
"bugs": {
|
"bugs": {
|
||||||
"url": "https://github.com/leon-ai/leon/issues"
|
"url": "https://github.com/leon-ai/leon/issues"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"axios": "1.4.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
40
bridges/nodejs/src/constants.ts
Normal file
40
bridges/nodejs/src/constants.ts
Normal file
@ -0,0 +1,40 @@
|
|||||||
|
import fs from 'node:fs'
|
||||||
|
import path from 'node:path'
|
||||||
|
|
||||||
|
import type { SkillConfigSchema } from '@server/schemas/skill-schemas'
|
||||||
|
|
||||||
|
import type { IntentObject } from '@sdk/types'
|
||||||
|
|
||||||
|
const {
|
||||||
|
argv: [, , INTENT_OBJ_FILE_PATH]
|
||||||
|
} = process
|
||||||
|
|
||||||
|
export const INTENT_OBJECT: IntentObject = JSON.parse(
|
||||||
|
fs.readFileSync(INTENT_OBJ_FILE_PATH as string, 'utf8')
|
||||||
|
)
|
||||||
|
export const SKILL_CONFIG: SkillConfigSchema = JSON.parse(
|
||||||
|
fs.readFileSync(
|
||||||
|
path.join(
|
||||||
|
process.cwd(),
|
||||||
|
'skills',
|
||||||
|
INTENT_OBJECT.domain,
|
||||||
|
INTENT_OBJECT.skill,
|
||||||
|
'config',
|
||||||
|
INTENT_OBJECT.lang + '.json'
|
||||||
|
),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
)
|
||||||
|
export const SKILL_SRC_CONFIG: Record<string, unknown> = JSON.parse(
|
||||||
|
fs.readFileSync(
|
||||||
|
path.join(
|
||||||
|
process.cwd(),
|
||||||
|
'skills',
|
||||||
|
INTENT_OBJECT.domain,
|
||||||
|
INTENT_OBJECT.skill,
|
||||||
|
'src',
|
||||||
|
'config.json'
|
||||||
|
),
|
||||||
|
'utf8'
|
||||||
|
)
|
||||||
|
).configurations
|
@ -1,5 +1,47 @@
|
|||||||
import { VERSION } from './version'
|
import path from 'node:path'
|
||||||
|
|
||||||
console.log('[WIP] Node.js bridge', VERSION)
|
import type { ActionFunction, ActionParams } from '@sdk/types'
|
||||||
|
import { INTENT_OBJECT } from '@bridge/constants'
|
||||||
|
;(async (): Promise<void> => {
|
||||||
|
const {
|
||||||
|
domain,
|
||||||
|
skill,
|
||||||
|
action,
|
||||||
|
lang,
|
||||||
|
utterance,
|
||||||
|
current_entities,
|
||||||
|
entities,
|
||||||
|
current_resolvers,
|
||||||
|
resolvers,
|
||||||
|
slots
|
||||||
|
} = INTENT_OBJECT
|
||||||
|
|
||||||
// TODO
|
const params: ActionParams = {
|
||||||
|
lang,
|
||||||
|
utterance,
|
||||||
|
current_entities,
|
||||||
|
entities,
|
||||||
|
current_resolvers,
|
||||||
|
resolvers,
|
||||||
|
slots
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const actionModule = await import(
|
||||||
|
path.join(
|
||||||
|
process.cwd(),
|
||||||
|
'skills',
|
||||||
|
domain,
|
||||||
|
skill,
|
||||||
|
'src',
|
||||||
|
'actions',
|
||||||
|
`${action}.ts`
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const actionFunction: ActionFunction = actionModule[action]
|
||||||
|
|
||||||
|
await actionFunction(params)
|
||||||
|
} catch (e) {
|
||||||
|
console.error(`Error while running "${skill}" skill "${action}" action:`, e)
|
||||||
|
}
|
||||||
|
})()
|
||||||
|
6
bridges/nodejs/src/sdk/aurora/button.ts
Normal file
6
bridges/nodejs/src/sdk/aurora/button.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
// TODO: contains the button API. rendering engine <-> SDK
|
||||||
|
export class Button {
|
||||||
|
constructor() {
|
||||||
|
console.log('Button constructor')
|
||||||
|
}
|
||||||
|
}
|
5
bridges/nodejs/src/sdk/aurora/index.ts
Normal file
5
bridges/nodejs/src/sdk/aurora/index.ts
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
import { Button } from './button'
|
||||||
|
|
||||||
|
export default {
|
||||||
|
Button
|
||||||
|
}
|
112
bridges/nodejs/src/sdk/leon.ts
Normal file
112
bridges/nodejs/src/sdk/leon.ts
Normal file
@ -0,0 +1,112 @@
|
|||||||
|
import type { AnswerData, AnswerInput, AnswerOutput } from '@sdk/types'
|
||||||
|
import {
|
||||||
|
INTENT_OBJECT,
|
||||||
|
SKILL_CONFIG,
|
||||||
|
SKILL_SRC_CONFIG
|
||||||
|
} from '@bridge/constants'
|
||||||
|
|
||||||
|
class Leon {
|
||||||
|
private static instance: Leon
|
||||||
|
|
||||||
|
constructor() {
|
||||||
|
if (!Leon.instance) {
|
||||||
|
Leon.instance = this
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get source configuration
|
||||||
|
* @example getSRCConfig() // { credentials: { apiKey: 'abc' } }
|
||||||
|
*/
|
||||||
|
public getSRCConfig<T>(key: string): T
|
||||||
|
public getSRCConfig<T extends Record<string, unknown>>(key?: undefined): T
|
||||||
|
public getSRCConfig<T extends Record<string, unknown> | unknown>(
|
||||||
|
key?: string
|
||||||
|
): T {
|
||||||
|
try {
|
||||||
|
if (key) {
|
||||||
|
return SKILL_SRC_CONFIG[key] as T
|
||||||
|
}
|
||||||
|
|
||||||
|
return SKILL_SRC_CONFIG as T
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error while getting source configuration:', e)
|
||||||
|
|
||||||
|
return {} as T
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Apply data to the answer
|
||||||
|
* @param answerKey The answer key
|
||||||
|
* @param data The data to apply
|
||||||
|
* @example setAnswerData('key', { name: 'Leon' })
|
||||||
|
*/
|
||||||
|
public setAnswerData(
|
||||||
|
answerKey: string,
|
||||||
|
data: AnswerData = null
|
||||||
|
): string | null {
|
||||||
|
try {
|
||||||
|
// In case the answer key is a raw answer
|
||||||
|
if (SKILL_CONFIG.answers == null || !SKILL_CONFIG.answers[answerKey]) {
|
||||||
|
return answerKey
|
||||||
|
}
|
||||||
|
|
||||||
|
const answers = SKILL_CONFIG.answers[answerKey] ?? ''
|
||||||
|
let answer: string
|
||||||
|
|
||||||
|
if (Array.isArray(answers)) {
|
||||||
|
answer = answers[Math.floor(Math.random() * answers.length)] ?? ''
|
||||||
|
} else {
|
||||||
|
answer = answers
|
||||||
|
}
|
||||||
|
|
||||||
|
if (data) {
|
||||||
|
for (const key in data) {
|
||||||
|
answer = answer.replaceAll(`%${key}%`, String(data[key]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (SKILL_CONFIG.variables) {
|
||||||
|
const { variables } = SKILL_CONFIG
|
||||||
|
|
||||||
|
for (const key in variables) {
|
||||||
|
answer = answer.replaceAll(`%${key}%`, String(variables[key]))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return answer
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error while setting answer data:', e)
|
||||||
|
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Send an answer to the core
|
||||||
|
* @param answerInput The answer input
|
||||||
|
* @example answer({ key: 'greet' }) // 'Hello world'
|
||||||
|
* @example answer({ key: 'welcome', data: { name: 'Louis' } }) // 'Welcome Louis'
|
||||||
|
* @example answer({ key: 'confirm', core: { restart: true } }) // 'Would you like to retry?'
|
||||||
|
*/
|
||||||
|
public async answer(answerInput: AnswerInput): Promise<void> {
|
||||||
|
try {
|
||||||
|
const answerObject: AnswerOutput = {
|
||||||
|
...INTENT_OBJECT,
|
||||||
|
output: {
|
||||||
|
codes: answerInput.key,
|
||||||
|
speech: this.setAnswerData(answerInput.key, answerInput.data) ?? '',
|
||||||
|
core: answerInput.core,
|
||||||
|
options: this.getSRCConfig('options')
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
process.stdout.write(JSON.stringify(answerObject))
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Error while creating answer:', e)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export const leon = new Leon()
|
96
bridges/nodejs/src/sdk/network.ts
Normal file
96
bridges/nodejs/src/sdk/network.ts
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
import axios from 'axios'
|
||||||
|
import type { AxiosInstance } from 'axios'
|
||||||
|
|
||||||
|
interface NetworkOptions {
|
||||||
|
/** `baseURL` will be prepended to `url`. It can be convenient to set `baseURL` for an instance of `Network` to pass relative URLs. */
|
||||||
|
baseURL?: string
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NetworkRequestOptions {
|
||||||
|
/** Server URL that will be used for the request. */
|
||||||
|
url: string
|
||||||
|
|
||||||
|
/** Request method to be used when making the request. */
|
||||||
|
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE'
|
||||||
|
|
||||||
|
/** Data to be sent as the request body. */
|
||||||
|
data?: Record<string, unknown>
|
||||||
|
|
||||||
|
/** Custom headers to be sent. */
|
||||||
|
headers?: Record<string, string>
|
||||||
|
}
|
||||||
|
|
||||||
|
interface NetworkResponse<ResponseData> {
|
||||||
|
/** Data provided by the server. */
|
||||||
|
data: ResponseData
|
||||||
|
|
||||||
|
/** HTTP status code from the server response. */
|
||||||
|
statusCode: number
|
||||||
|
|
||||||
|
/** Options that was provided for the request. */
|
||||||
|
options: NetworkRequestOptions & NetworkOptions
|
||||||
|
}
|
||||||
|
|
||||||
|
export class NetworkError<ResponseErrorData = unknown> extends Error {
|
||||||
|
public readonly response: NetworkResponse<ResponseErrorData>
|
||||||
|
|
||||||
|
public constructor(response: NetworkResponse<ResponseErrorData>) {
|
||||||
|
super(`[NetworkError]: ${response.statusCode}`)
|
||||||
|
this.response = response
|
||||||
|
Object.setPrototypeOf(this, NetworkError.prototype)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
export class Network {
|
||||||
|
private options: NetworkOptions
|
||||||
|
private axios: AxiosInstance
|
||||||
|
|
||||||
|
public constructor(options: NetworkOptions = {}) {
|
||||||
|
this.options = options
|
||||||
|
this.axios = axios.create({
|
||||||
|
baseURL: this.options.baseURL
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
public async request<ResponseData = unknown, ResponseErrorData = unknown>(
|
||||||
|
options: NetworkRequestOptions
|
||||||
|
): Promise<NetworkResponse<ResponseData>> {
|
||||||
|
try {
|
||||||
|
const response = await this.axios.request<ResponseData>({
|
||||||
|
url: options.url,
|
||||||
|
method: options.method.toLowerCase(),
|
||||||
|
data: options.data,
|
||||||
|
headers: options.headers
|
||||||
|
})
|
||||||
|
return {
|
||||||
|
data: response.data,
|
||||||
|
statusCode: response.status,
|
||||||
|
options: {
|
||||||
|
...this.options,
|
||||||
|
...options
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
let statusCode = 500
|
||||||
|
let data = {} as ResponseErrorData
|
||||||
|
if (axios.isAxiosError(error)) {
|
||||||
|
data = error?.response?.data
|
||||||
|
statusCode = error?.response?.status ?? 500
|
||||||
|
}
|
||||||
|
throw new NetworkError<ResponseErrorData>({
|
||||||
|
data,
|
||||||
|
statusCode,
|
||||||
|
options: {
|
||||||
|
...this.options,
|
||||||
|
...options
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public isNetworkError<ResponseErrorData = unknown>(
|
||||||
|
error: unknown
|
||||||
|
): error is NetworkError<ResponseErrorData> {
|
||||||
|
return error instanceof NetworkError
|
||||||
|
}
|
||||||
|
}
|
39
bridges/nodejs/src/sdk/types.ts
Normal file
39
bridges/nodejs/src/sdk/types.ts
Normal file
@ -0,0 +1,39 @@
|
|||||||
|
/**
|
||||||
|
* Action types
|
||||||
|
*/
|
||||||
|
import type { ActionParams, IntentObject } from '@/core/brain/types'
|
||||||
|
|
||||||
|
export type { ActionParams, IntentObject }
|
||||||
|
|
||||||
|
export type ActionFunction = (params: ActionParams) => Promise<void>
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Answer types
|
||||||
|
*/
|
||||||
|
export interface AnswerOutput extends IntentObject {
|
||||||
|
output: {
|
||||||
|
codes: string
|
||||||
|
speech: string
|
||||||
|
core?: AnswerCoreData
|
||||||
|
options: Record<string, string>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
export interface AnswerCoreData {
|
||||||
|
restart?: boolean
|
||||||
|
isInActionLoop?: boolean
|
||||||
|
showNextActionSuggestions?: boolean
|
||||||
|
showSuggestions?: boolean
|
||||||
|
}
|
||||||
|
export interface TextAnswer {
|
||||||
|
key: string
|
||||||
|
data?: AnswerData
|
||||||
|
core?: AnswerCoreData
|
||||||
|
}
|
||||||
|
export interface WidgetAnswer {
|
||||||
|
// TODO
|
||||||
|
key: 'widget'
|
||||||
|
data?: AnswerData
|
||||||
|
core?: AnswerCoreData
|
||||||
|
}
|
||||||
|
export type AnswerData = Record<string, string | number> | null
|
||||||
|
export type AnswerInput = TextAnswer | WidgetAnswer
|
@ -1,23 +1,24 @@
|
|||||||
{
|
{
|
||||||
"extends": "@tsconfig/node16-strictest/tsconfig.json",
|
"extends": "../../tsconfig.json",
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"rootDir": "./src",
|
|
||||||
"outDir": "./dist/bin",
|
"outDir": "./dist/bin",
|
||||||
|
"rootDir": "../../",
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@/*": ["./src/*"]
|
"@@/*": ["../../*"],
|
||||||
|
"@/*": ["../../server/src/*"],
|
||||||
|
"@server/*": ["../../server/src/*"],
|
||||||
|
"@bridge/*": ["./src/*"],
|
||||||
|
"@sdk/*": ["./src/sdk/*"]
|
||||||
},
|
},
|
||||||
"ignoreDeprecations": "5.0",
|
"exactOptionalPropertyTypes": false,
|
||||||
"allowJs": true,
|
"declaration": true
|
||||||
"checkJs": false,
|
|
||||||
"resolveJsonModule": true
|
|
||||||
},
|
},
|
||||||
"ts-node": {
|
"ts-node": {
|
||||||
"swc": true,
|
"swc": true,
|
||||||
"require": ["tsconfig-paths/register"],
|
"require": ["tsconfig-paths/register"],
|
||||||
"files": true
|
"files": true
|
||||||
},
|
},
|
||||||
"files": [],
|
|
||||||
"include": ["src/**/*"],
|
"include": ["src/**/*"],
|
||||||
"exclude": ["dist"]
|
"exclude": ["node_modules", "dist"]
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,7 @@ def translate(key, dict = { }):
|
|||||||
"""Pickup the language file according to the cmd arg
|
"""Pickup the language file according to the cmd arg
|
||||||
and return the value according to the params"""
|
and return the value according to the params"""
|
||||||
|
|
||||||
# "Temporize" for the data buffer ouput on the core
|
# "Temporize" for the data buffer output on the core
|
||||||
sleep(0.1)
|
sleep(0.1)
|
||||||
|
|
||||||
output = ''
|
output = ''
|
||||||
@ -82,6 +82,7 @@ def output(type, content = '', core = { }):
|
|||||||
'entities': intent_obj['entities'],
|
'entities': intent_obj['entities'],
|
||||||
'slots': intent_obj['slots'],
|
'slots': intent_obj['slots'],
|
||||||
'output': {
|
'output': {
|
||||||
|
# TODO: remove type as it is not needed anymore
|
||||||
'type': type,
|
'type': type,
|
||||||
'codes': codes,
|
'codes': codes,
|
||||||
'speech': speech,
|
'speech': speech,
|
||||||
@ -90,8 +91,7 @@ def output(type, content = '', core = { }):
|
|||||||
}
|
}
|
||||||
}))
|
}))
|
||||||
|
|
||||||
if (type == 'inter'):
|
sys.stdout.flush()
|
||||||
sys.stdout.flush()
|
|
||||||
|
|
||||||
def http(method, url, headers = None):
|
def http(method, url, headers = None):
|
||||||
"""Send HTTP request with the Leon user agent"""
|
"""Send HTTP request with the Leon user agent"""
|
||||||
|
@ -141,6 +141,11 @@
|
|||||||
"route": "/api/action/games/rochambeau/rematch",
|
"route": "/api/action/games/rochambeau/rematch",
|
||||||
"params": []
|
"params": []
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"method": "GET",
|
||||||
|
"route": "/api/action/leon/age/run",
|
||||||
|
"params": []
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"method": "GET",
|
"method": "GET",
|
||||||
"route": "/api/action/leon/color/favorite_color",
|
"route": "/api/action/leon/color/favorite_color",
|
||||||
|
@ -117,6 +117,7 @@
|
|||||||
"@types/node-wav": "0.0.0",
|
"@types/node-wav": "0.0.0",
|
||||||
"@typescript-eslint/eslint-plugin": "5.55.0",
|
"@typescript-eslint/eslint-plugin": "5.55.0",
|
||||||
"@typescript-eslint/parser": "5.55.0",
|
"@typescript-eslint/parser": "5.55.0",
|
||||||
|
"@vercel/ncc": "0.36.1",
|
||||||
"cli-spinner": "0.2.10",
|
"cli-spinner": "0.2.10",
|
||||||
"eslint": "8.22.0",
|
"eslint": "8.22.0",
|
||||||
"eslint-config-prettier": "8.5.0",
|
"eslint-config-prettier": "8.5.0",
|
||||||
|
12
scripts/assets/nodejs-bridge-intent-object.json
Normal file
12
scripts/assets/nodejs-bridge-intent-object.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"lang": "en",
|
||||||
|
"domain": "leon",
|
||||||
|
"skill": "age",
|
||||||
|
"action": "run",
|
||||||
|
"utterance": "How old are you?",
|
||||||
|
"slots": {},
|
||||||
|
"entities": [],
|
||||||
|
"current_entities": [],
|
||||||
|
"resolvers": [],
|
||||||
|
"current_resolvers": []
|
||||||
|
}
|
@ -137,19 +137,25 @@ BUILD_TARGETS.set('tcp-server', {
|
|||||||
* Build for binaries not requiring a Python environment
|
* Build for binaries not requiring a Python environment
|
||||||
*/
|
*/
|
||||||
try {
|
try {
|
||||||
const tsconfigPath = path.join(NODEJS_BRIDGE_ROOT_PATH, 'tsconfig.json')
|
const distBinPath = path.join(NODEJS_BRIDGE_DIST_PATH, 'bin')
|
||||||
const distMainFilePath = path.join(
|
const distMainFilePath = path.join(
|
||||||
NODEJS_BRIDGE_DIST_PATH,
|
distBinPath,
|
||||||
'bin',
|
'index.js'
|
||||||
'main.js'
|
|
||||||
)
|
)
|
||||||
const distRenamedMainFilePath = path.join(
|
const distRenamedMainFilePath = path.join(
|
||||||
NODEJS_BRIDGE_DIST_PATH,
|
distBinPath,
|
||||||
'bin',
|
|
||||||
NODEJS_BRIDGE_BIN_NAME
|
NODEJS_BRIDGE_BIN_NAME
|
||||||
)
|
)
|
||||||
|
|
||||||
await command(`tsc --project ${tsconfigPath}`, {
|
await fs.promises.rm(buildPath, { recursive: true, force: true })
|
||||||
|
|
||||||
|
const inputMainFilePath = path.join(
|
||||||
|
NODEJS_BRIDGE_ROOT_PATH,
|
||||||
|
'src',
|
||||||
|
'main.ts'
|
||||||
|
)
|
||||||
|
|
||||||
|
await command(`ncc build ${inputMainFilePath} --out ${distBinPath}`, {
|
||||||
shell: true,
|
shell: true,
|
||||||
stdio: 'inherit'
|
stdio: 'inherit'
|
||||||
})
|
})
|
||||||
|
@ -16,9 +16,11 @@ import { SystemHelper } from '@/helpers/system-helper'
|
|||||||
import {
|
import {
|
||||||
MINIMUM_REQUIRED_RAM,
|
MINIMUM_REQUIRED_RAM,
|
||||||
LEON_VERSION,
|
LEON_VERSION,
|
||||||
|
NODEJS_BRIDGE_BIN_PATH,
|
||||||
PYTHON_BRIDGE_BIN_PATH,
|
PYTHON_BRIDGE_BIN_PATH,
|
||||||
TCP_SERVER_BIN_PATH,
|
TCP_SERVER_BIN_PATH,
|
||||||
TCP_SERVER_VERSION,
|
TCP_SERVER_VERSION,
|
||||||
|
NODEJS_BRIDGE_VERSION,
|
||||||
PYTHON_BRIDGE_VERSION,
|
PYTHON_BRIDGE_VERSION,
|
||||||
INSTANCE_ID
|
INSTANCE_ID
|
||||||
} from '@/constants'
|
} from '@/constants'
|
||||||
@ -102,6 +104,13 @@ dotenv.config()
|
|||||||
skillsResolversModelState: null,
|
skillsResolversModelState: null,
|
||||||
mainModelState: null
|
mainModelState: null
|
||||||
},
|
},
|
||||||
|
nodeJSBridge: {
|
||||||
|
version: null,
|
||||||
|
executionTime: null,
|
||||||
|
command: null,
|
||||||
|
output: null,
|
||||||
|
error: null
|
||||||
|
},
|
||||||
pythonBridge: {
|
pythonBridge: {
|
||||||
version: null,
|
version: null,
|
||||||
executionTime: null,
|
executionTime: null,
|
||||||
@ -209,7 +218,41 @@ dotenv.config()
|
|||||||
})
|
})
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skill execution checking
|
* Skill execution checking with Node.js bridge
|
||||||
|
*/
|
||||||
|
|
||||||
|
LogHelper.success(`Node.js bridge version: ${NODEJS_BRIDGE_VERSION}`)
|
||||||
|
reportDataInput.nodeJSBridge.version = NODEJS_BRIDGE_VERSION
|
||||||
|
LogHelper.info('Executing a skill...')
|
||||||
|
|
||||||
|
try {
|
||||||
|
const executionStart = Date.now()
|
||||||
|
const p = await command(
|
||||||
|
`${NODEJS_BRIDGE_BIN_PATH} "${path.join(
|
||||||
|
process.cwd(),
|
||||||
|
'scripts',
|
||||||
|
'assets',
|
||||||
|
'nodejs-bridge-intent-object.json'
|
||||||
|
)}"`,
|
||||||
|
{ shell: true }
|
||||||
|
)
|
||||||
|
const executionEnd = Date.now()
|
||||||
|
const executionTime = executionEnd - executionStart
|
||||||
|
LogHelper.info(p.command)
|
||||||
|
reportDataInput.nodeJSBridge.command = p.command
|
||||||
|
LogHelper.success(p.stdout)
|
||||||
|
reportDataInput.nodeJSBridge.output = p.stdout
|
||||||
|
LogHelper.info(`Skill execution time: ${executionTime}ms\n`)
|
||||||
|
reportDataInput.nodeJSBridge.executionTime = `${executionTime}ms`
|
||||||
|
} catch (e) {
|
||||||
|
LogHelper.info(e.command)
|
||||||
|
report.can_run_skill.v = false
|
||||||
|
LogHelper.error(`${e}\n`)
|
||||||
|
reportDataInput.nodeJSBridge.error = JSON.stringify(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skill execution checking with Python bridge
|
||||||
*/
|
*/
|
||||||
|
|
||||||
LogHelper.success(`Python bridge version: ${PYTHON_BRIDGE_VERSION}`)
|
LogHelper.success(`Python bridge version: ${PYTHON_BRIDGE_VERSION}`)
|
||||||
@ -223,7 +266,7 @@ dotenv.config()
|
|||||||
process.cwd(),
|
process.cwd(),
|
||||||
'scripts',
|
'scripts',
|
||||||
'assets',
|
'assets',
|
||||||
'intent-object.json'
|
'python-bridge-intent-object.json'
|
||||||
)}"`,
|
)}"`,
|
||||||
{ shell: true }
|
{ shell: true }
|
||||||
)
|
)
|
||||||
|
@ -72,11 +72,13 @@ export const PYTHON_BRIDGE_BIN_PATH = path.join(
|
|||||||
BINARIES_FOLDER_NAME,
|
BINARIES_FOLDER_NAME,
|
||||||
PYTHON_BRIDGE_BIN_NAME
|
PYTHON_BRIDGE_BIN_NAME
|
||||||
)
|
)
|
||||||
export const NODEJS_BRIDGE_BIN_PATH = `${process.execPath} ${path.join(
|
export const NODEJS_BRIDGE_BIN_PATH = `${path.join(
|
||||||
NODEJS_BRIDGE_DIST_PATH,
|
process.cwd(),
|
||||||
'bin',
|
'node_modules',
|
||||||
NODEJS_BRIDGE_BIN_NAME
|
'ts-node',
|
||||||
)}`
|
'dist',
|
||||||
|
'bin.js'
|
||||||
|
)} --swc ${path.join(NODEJS_BRIDGE_DIST_PATH, 'bin', NODEJS_BRIDGE_BIN_NAME)}`
|
||||||
|
|
||||||
export const LEON_VERSION = process.env['npm_package_version']
|
export const LEON_VERSION = process.env['npm_package_version']
|
||||||
|
|
||||||
|
@ -15,11 +15,7 @@ import type {
|
|||||||
IntentObject,
|
IntentObject,
|
||||||
SkillResult
|
SkillResult
|
||||||
} from '@/core/brain/types'
|
} from '@/core/brain/types'
|
||||||
import {
|
import { SkillActionTypes, SkillBridges } from '@/core/brain/types'
|
||||||
SkillActionTypes,
|
|
||||||
SkillBridges,
|
|
||||||
SkillOutputTypes
|
|
||||||
} from '@/core/brain/types'
|
|
||||||
import { langs } from '@@/core/langs.json'
|
import { langs } from '@@/core/langs.json'
|
||||||
import {
|
import {
|
||||||
HAS_TTS,
|
HAS_TTS,
|
||||||
@ -199,18 +195,15 @@ export default class Brain {
|
|||||||
const obj = JSON.parse(data.toString())
|
const obj = JSON.parse(data.toString())
|
||||||
|
|
||||||
if (typeof obj === 'object') {
|
if (typeof obj === 'object') {
|
||||||
if (obj.output.type === SkillOutputTypes.Intermediate) {
|
LogHelper.title(`${this.skillFriendlyName} skill (on data)`)
|
||||||
LogHelper.title(`${this.skillFriendlyName} skill`)
|
LogHelper.info(data.toString())
|
||||||
LogHelper.info(data.toString())
|
|
||||||
|
|
||||||
const speech = obj.output.speech.toString()
|
const speech = obj.output.speech.toString()
|
||||||
if (!this.isMuted) {
|
if (!this.isMuted) {
|
||||||
this.talk(speech)
|
this.talk(speech)
|
||||||
}
|
|
||||||
this.speeches.push(speech)
|
|
||||||
} else {
|
|
||||||
this.skillOutput = data.toString()
|
|
||||||
}
|
}
|
||||||
|
this.speeches.push(speech)
|
||||||
|
this.skillOutput = data.toString()
|
||||||
|
|
||||||
return Promise.resolve(null)
|
return Promise.resolve(null)
|
||||||
} else {
|
} else {
|
||||||
@ -388,7 +381,7 @@ export default class Brain {
|
|||||||
|
|
||||||
// Catch the end of the skill execution
|
// Catch the end of the skill execution
|
||||||
this.skillProcess?.stdout.on('end', () => {
|
this.skillProcess?.stdout.on('end', () => {
|
||||||
LogHelper.title(`${this.skillFriendlyName} skill`)
|
LogHelper.title(`${this.skillFriendlyName} skill (on end)`)
|
||||||
LogHelper.info(this.skillOutput)
|
LogHelper.info(this.skillOutput)
|
||||||
|
|
||||||
let skillResult: SkillResult | undefined = undefined
|
let skillResult: SkillResult | undefined = undefined
|
||||||
@ -398,36 +391,26 @@ export default class Brain {
|
|||||||
try {
|
try {
|
||||||
skillResult = JSON.parse(this.skillOutput)
|
skillResult = JSON.parse(this.skillOutput)
|
||||||
|
|
||||||
if (skillResult?.output.speech) {
|
// Synchronize the downloaded content if enabled
|
||||||
skillResult.output.speech =
|
if (
|
||||||
skillResult.output.speech.toString()
|
skillResult &&
|
||||||
if (!this.isMuted) {
|
skillResult.output.options['synchronization'] &&
|
||||||
this.talk(skillResult.output.speech, true)
|
skillResult.output.options['synchronization'].enabled &&
|
||||||
}
|
skillResult.output.options['synchronization'].enabled === true
|
||||||
speeches.push(skillResult.output.speech)
|
) {
|
||||||
|
const sync = new Synchronizer(
|
||||||
|
this,
|
||||||
|
nluResult.classification,
|
||||||
|
skillResult.output.options['synchronization']
|
||||||
|
)
|
||||||
|
|
||||||
// Synchronize the downloaded content if enabled
|
// When the synchronization is finished
|
||||||
if (
|
sync.synchronize((speech: string) => {
|
||||||
skillResult.output.type === SkillOutputTypes.End &&
|
if (!this.isMuted) {
|
||||||
skillResult.output.options['synchronization'] &&
|
this.talk(speech)
|
||||||
skillResult.output.options['synchronization'].enabled &&
|
}
|
||||||
skillResult.output.options['synchronization'].enabled ===
|
speeches.push(speech)
|
||||||
true
|
})
|
||||||
) {
|
|
||||||
const sync = new Synchronizer(
|
|
||||||
this,
|
|
||||||
nluResult.classification,
|
|
||||||
skillResult.output.options['synchronization']
|
|
||||||
)
|
|
||||||
|
|
||||||
// When the synchronization is finished
|
|
||||||
sync.synchronize((speech: string) => {
|
|
||||||
if (!this.isMuted) {
|
|
||||||
this.talk(speech)
|
|
||||||
}
|
|
||||||
speeches.push(speech)
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
LogHelper.title(`${this.skillFriendlyName} skill`)
|
LogHelper.title(`${this.skillFriendlyName} skill`)
|
||||||
|
@ -28,7 +28,6 @@ export interface SkillResult {
|
|||||||
entities: NEREntity[]
|
entities: NEREntity[]
|
||||||
slots: NLUSlots
|
slots: NLUSlots
|
||||||
output: {
|
output: {
|
||||||
type: SkillOutputTypes
|
|
||||||
codes: string[]
|
codes: string[]
|
||||||
speech: string
|
speech: string
|
||||||
core: SkillCoreData | undefined
|
core: SkillCoreData | undefined
|
||||||
@ -41,21 +40,13 @@ export enum SkillBridges {
|
|||||||
Python = 'python',
|
Python = 'python',
|
||||||
NodeJS = 'nodejs'
|
NodeJS = 'nodejs'
|
||||||
}
|
}
|
||||||
export enum SkillOutputTypes {
|
|
||||||
Intermediate = 'inter',
|
|
||||||
End = 'end'
|
|
||||||
}
|
|
||||||
export enum SkillActionTypes {
|
export enum SkillActionTypes {
|
||||||
Logic = 'logic',
|
Logic = 'logic',
|
||||||
Dialog = 'dialog'
|
Dialog = 'dialog'
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface IntentObject {
|
export interface ActionParams {
|
||||||
id: string
|
|
||||||
lang: ShortLanguageCode
|
lang: ShortLanguageCode
|
||||||
domain: NLPDomain
|
|
||||||
skill: NLPSkill
|
|
||||||
action: NLPAction
|
|
||||||
utterance: NLPUtterance
|
utterance: NLPUtterance
|
||||||
current_entities: NEREntity[]
|
current_entities: NEREntity[]
|
||||||
entities: NEREntity[]
|
entities: NEREntity[]
|
||||||
@ -64,6 +55,13 @@ export interface IntentObject {
|
|||||||
slots: { [key: string]: NLUSlot['value'] | undefined }
|
slots: { [key: string]: NLUSlot['value'] | undefined }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface IntentObject extends ActionParams {
|
||||||
|
id: string
|
||||||
|
domain: NLPDomain
|
||||||
|
skill: NLPSkill
|
||||||
|
action: NLPAction
|
||||||
|
}
|
||||||
|
|
||||||
export interface BrainProcessResult extends NLUResult {
|
export interface BrainProcessResult extends NLUResult {
|
||||||
speeches: string[]
|
speeches: string[]
|
||||||
executionTime: number
|
executionTime: number
|
||||||
|
@ -8,7 +8,7 @@ enum OSNames {
|
|||||||
Linux = 'Linux',
|
Linux = 'Linux',
|
||||||
Unknown = 'Unknown'
|
Unknown = 'Unknown'
|
||||||
}
|
}
|
||||||
enum BinaryFolderNames {
|
export enum BinaryFolderNames {
|
||||||
Linux64Bit = 'linux-x86_64', // Linux 64-bit (Intel)
|
Linux64Bit = 'linux-x86_64', // Linux 64-bit (Intel)
|
||||||
LinuxARM64 = 'linux-aarch64', // Linux 64-bit (ARM)
|
LinuxARM64 = 'linux-aarch64', // Linux 64-bit (ARM)
|
||||||
MacOS64Bit = 'macosx-x86_64', // Apple 64-bit (Intel)
|
MacOS64Bit = 'macosx-x86_64', // Apple 64-bit (Intel)
|
||||||
|
0
skills/leon/age/README.md
Normal file
0
skills/leon/age/README.md
Normal file
14
skills/leon/age/config/en.json
Normal file
14
skills/leon/age/config/en.json
Normal file
@ -0,0 +1,14 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../../../schemas/skill-schemas/skill-config.json",
|
||||||
|
"actions": {
|
||||||
|
"run": {
|
||||||
|
"type": "logic",
|
||||||
|
"utterance_samples": ["How old are you?"]
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"answers": {
|
||||||
|
"default": ["I'm..."],
|
||||||
|
"greet": ["Hey, just a try %name% again %name%", "Another try, hi"],
|
||||||
|
"answer": ["%answer%"]
|
||||||
|
}
|
||||||
|
}
|
0
skills/leon/age/memory/.gitkeep
Normal file
0
skills/leon/age/memory/.gitkeep
Normal file
12
skills/leon/age/skill.json
Normal file
12
skills/leon/age/skill.json
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../../../schemas/skill-schemas/skill.json",
|
||||||
|
"name": "Age",
|
||||||
|
"bridge": "nodejs",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "Leon tells his age.",
|
||||||
|
"author": {
|
||||||
|
"name": "Louis Grenard",
|
||||||
|
"email": "louis@getleon.ai",
|
||||||
|
"url": "https://github.com/louistiti"
|
||||||
|
}
|
||||||
|
}
|
62
skills/leon/age/src/actions/run.ts
Normal file
62
skills/leon/age/src/actions/run.ts
Normal file
@ -0,0 +1,62 @@
|
|||||||
|
import type { ActionFunction } from '@sdk/types'
|
||||||
|
import { leon } from '@sdk/leon'
|
||||||
|
import { Network } from '@sdk/network'
|
||||||
|
import { Button } from '@sdk/aurora/button'
|
||||||
|
|
||||||
|
export const run: ActionFunction = async function () {
|
||||||
|
await leon.answer({ key: 'default' })
|
||||||
|
|
||||||
|
await leon.answer({
|
||||||
|
key: 'greet',
|
||||||
|
data: {
|
||||||
|
name: 'Louis'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
console.log('button', Button)
|
||||||
|
|
||||||
|
const { someSampleConfig } = leon.getSRCConfig<{
|
||||||
|
options: { someSampleConfig: string }
|
||||||
|
}>()['options']
|
||||||
|
|
||||||
|
const options = leon.getSRCConfig<{ someSampleConfig: string }>('options')
|
||||||
|
await leon.answer({
|
||||||
|
key: 'answer',
|
||||||
|
data: {
|
||||||
|
answer: options.someSampleConfig + someSampleConfig
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
const network = new Network({
|
||||||
|
baseURL: 'https://jsonplaceholder.typicode.com'
|
||||||
|
})
|
||||||
|
|
||||||
|
try {
|
||||||
|
const response = await network.request<{ title: string }>({
|
||||||
|
url: '/todos/1',
|
||||||
|
method: 'GET'
|
||||||
|
})
|
||||||
|
await leon.answer({
|
||||||
|
key: 'answer',
|
||||||
|
data: {
|
||||||
|
answer: `Todo: ${response.data.title}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
} catch (error) {
|
||||||
|
await leon.answer({
|
||||||
|
key: 'answer',
|
||||||
|
data: {
|
||||||
|
answer: 'Something went wrong...'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
if (network.isNetworkError(error)) {
|
||||||
|
const errorData = JSON.stringify(error.response.data, null, 2)
|
||||||
|
await leon.answer({
|
||||||
|
key: 'answer',
|
||||||
|
data: {
|
||||||
|
answer: `${error.message}: ${errorData}`
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
6
skills/leon/age/src/config.sample.json
Normal file
6
skills/leon/age/src/config.sample.json
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
{
|
||||||
|
"configurations": {
|
||||||
|
"options": {},
|
||||||
|
"credentials": {}
|
||||||
|
}
|
||||||
|
}
|
0
skills/leon/age/src/lib/.gitkeep
Normal file
0
skills/leon/age/src/lib/.gitkeep
Normal file
0
skills/leon/age/test/.gitkeep
Normal file
0
skills/leon/age/test/.gitkeep
Normal file
9
skills/tsconfig.json
Normal file
9
skills/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
|||||||
|
{
|
||||||
|
"extends": "@tsconfig/node16-strictest/tsconfig.json",
|
||||||
|
"compilerOptions": {
|
||||||
|
"paths": {
|
||||||
|
"@bridge/*": ["../bridges/nodejs/src/*"],
|
||||||
|
"@sdk/*": ["../bridges/nodejs/src/sdk/*"]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -6,12 +6,15 @@
|
|||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"paths": {
|
"paths": {
|
||||||
"@@/*": ["./*"],
|
"@@/*": ["./*"],
|
||||||
"@/*": ["./server/src/*"]
|
"@/*": ["./server/src/*"],
|
||||||
|
"@bridge/*": ["./bridges/nodejs/src/*"],
|
||||||
|
"@sdk/*": ["./bridges/nodejs/src/sdk/*"]
|
||||||
},
|
},
|
||||||
"ignoreDeprecations": "5.0",
|
"ignoreDeprecations": "5.0",
|
||||||
"allowJs": true,
|
"allowJs": true,
|
||||||
"checkJs": false,
|
"checkJs": false,
|
||||||
"resolveJsonModule": true
|
"resolveJsonModule": true,
|
||||||
|
"declaration": true
|
||||||
},
|
},
|
||||||
"ts-node": {
|
"ts-node": {
|
||||||
"swc": true,
|
"swc": true,
|
||||||
@ -20,5 +23,5 @@
|
|||||||
},
|
},
|
||||||
"files": ["server/src/global.d.ts"],
|
"files": ["server/src/global.d.ts"],
|
||||||
"include": ["server/src/**/*"],
|
"include": ["server/src/**/*"],
|
||||||
"exclude": ["node_modules", "server/dist", "bridges/python", "tcp_server"]
|
"exclude": ["node_modules", "server/dist", "bridges", "tcp_server"]
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user