From 9b839d3e6616ecc2563bf4b747fc7e3d60162b2b Mon Sep 17 00:00:00 2001 From: louistiti Date: Tue, 2 May 2023 23:53:41 +0800 Subject: [PATCH 01/19] feat(bridge/nodejs): first draft --- bridges/nodejs/src/main.ts | 72 ++++++++++++++++++++++++-- bridges/nodejs/tsconfig.json | 19 ++----- core/skills-endpoints.json | 5 ++ server/src/constants.ts | 12 +++-- skills/leon/age/README.md | 0 skills/leon/age/config/en.json | 12 +++++ skills/leon/age/memory/.gitkeep | 0 skills/leon/age/skill.json | 12 +++++ skills/leon/age/src/actions/run.ts | 3 ++ skills/leon/age/src/config.sample.json | 6 +++ skills/leon/age/src/lib/.gitkeep | 0 skills/leon/age/test/.gitkeep | 0 12 files changed, 117 insertions(+), 24 deletions(-) create mode 100644 skills/leon/age/README.md create mode 100644 skills/leon/age/config/en.json create mode 100644 skills/leon/age/memory/.gitkeep create mode 100644 skills/leon/age/skill.json create mode 100644 skills/leon/age/src/actions/run.ts create mode 100644 skills/leon/age/src/config.sample.json create mode 100644 skills/leon/age/src/lib/.gitkeep create mode 100644 skills/leon/age/test/.gitkeep diff --git a/bridges/nodejs/src/main.ts b/bridges/nodejs/src/main.ts index c2b7f186..4a3747aa 100644 --- a/bridges/nodejs/src/main.ts +++ b/bridges/nodejs/src/main.ts @@ -1,5 +1,71 @@ -import { VERSION } from './version' +import fs from 'node:fs' +import path from 'node:path' -console.log('[WIP] Node.js bridge', VERSION) +const { + argv: [, , INTENT_OBJ_FILE_PATH] +} = process -// TODO +;(async (): Promise => { + if (INTENT_OBJ_FILE_PATH) { + const { + domain, + skill, + action, + lang, + utterance, + current_entities: currentEntities, + entities, + current_resolvers: currentResolvers, + resolvers, + slots + } = JSON.parse(await fs.promises.readFile(INTENT_OBJ_FILE_PATH, 'utf8')) + + const params = { + lang, + utterance, + currentEntities, + entities, + currentResolvers, + resolvers, + slots + } + + try { + const { [action]: actionFunction } = await import( + path.join( + process.cwd(), + 'skills', + domain, + skill, + 'src', + 'actions', + `${action}.ts` + ) + ) + + const speech = actionFunction(params) + + console.log( + JSON.stringify({ + domain, + skill, + action, + lang, + utterance, + entities, + slots, + // TODO + output: { + type: 'end', + codes: '', + speech, + core: {}, + options: {} + } + }) + ) + } catch (e) { + console.error('Error while running action:', e) + } + } +})() diff --git a/bridges/nodejs/tsconfig.json b/bridges/nodejs/tsconfig.json index 27f9d35a..61c5397a 100644 --- a/bridges/nodejs/tsconfig.json +++ b/bridges/nodejs/tsconfig.json @@ -1,23 +1,10 @@ { - "extends": "@tsconfig/node16-strictest/tsconfig.json", + "extends": "../../tsconfig.json", "compilerOptions": { "rootDir": "./src", "outDir": "./dist/bin", - "baseUrl": ".", - "paths": { - "@/*": ["./src/*"] - }, - "ignoreDeprecations": "5.0", - "allowJs": true, - "checkJs": false, - "resolveJsonModule": true + "baseUrl": "." }, - "ts-node": { - "swc": true, - "require": ["tsconfig-paths/register"], - "files": true - }, - "files": [], "include": ["src/**/*"], - "exclude": ["dist"] + "exclude": ["node_modules", "dist"] } diff --git a/core/skills-endpoints.json b/core/skills-endpoints.json index 125d2169..459c3b34 100644 --- a/core/skills-endpoints.json +++ b/core/skills-endpoints.json @@ -141,6 +141,11 @@ "route": "/api/action/games/rochambeau/rematch", "params": [] }, + { + "method": "GET", + "route": "/api/action/leon/age/run", + "params": [] + }, { "method": "GET", "route": "/api/action/leon/color/favorite_color", diff --git a/server/src/constants.ts b/server/src/constants.ts index 44c8dcd3..71bd8d9c 100644 --- a/server/src/constants.ts +++ b/server/src/constants.ts @@ -72,11 +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 NODEJS_BRIDGE_BIN_PATH = `${path.join( + process.cwd(), + 'node_modules', + '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'] diff --git a/skills/leon/age/README.md b/skills/leon/age/README.md new file mode 100644 index 00000000..e69de29b diff --git a/skills/leon/age/config/en.json b/skills/leon/age/config/en.json new file mode 100644 index 00000000..f57882bb --- /dev/null +++ b/skills/leon/age/config/en.json @@ -0,0 +1,12 @@ +{ + "$schema": "../../../../schemas/skill-schemas/skill-config.json", + "actions": { + "run": { + "type": "logic", + "utterance_samples": ["How old are you?"] + } + }, + "answers": { + "default": ["I'm..."] + } +} diff --git a/skills/leon/age/memory/.gitkeep b/skills/leon/age/memory/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/skills/leon/age/skill.json b/skills/leon/age/skill.json new file mode 100644 index 00000000..72322cb6 --- /dev/null +++ b/skills/leon/age/skill.json @@ -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" + } +} diff --git a/skills/leon/age/src/actions/run.ts b/skills/leon/age/src/actions/run.ts new file mode 100644 index 00000000..a5b27972 --- /dev/null +++ b/skills/leon/age/src/actions/run.ts @@ -0,0 +1,3 @@ +export function run(): string { + return 'hello' +} diff --git a/skills/leon/age/src/config.sample.json b/skills/leon/age/src/config.sample.json new file mode 100644 index 00000000..9e43d47e --- /dev/null +++ b/skills/leon/age/src/config.sample.json @@ -0,0 +1,6 @@ +{ + "configurations": { + "options": {}, + "credentials": {} + } +} diff --git a/skills/leon/age/src/lib/.gitkeep b/skills/leon/age/src/lib/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/skills/leon/age/test/.gitkeep b/skills/leon/age/test/.gitkeep new file mode 100644 index 00000000..e69de29b From 2df22770e0547261341c72e688c4c1478e4be9cf Mon Sep 17 00:00:00 2001 From: louistiti Date: Wed, 3 May 2023 08:39:06 +0800 Subject: [PATCH 02/19] refactor(bridge/nodejs): SDK mapping --- bridges/nodejs/src/sdk/testo.ts | 3 +++ skills/leon/age/src/actions/run.ts | 4 +++- tsconfig.json | 5 +++-- 3 files changed, 9 insertions(+), 3 deletions(-) create mode 100644 bridges/nodejs/src/sdk/testo.ts diff --git a/bridges/nodejs/src/sdk/testo.ts b/bridges/nodejs/src/sdk/testo.ts new file mode 100644 index 00000000..32f20dcb --- /dev/null +++ b/bridges/nodejs/src/sdk/testo.ts @@ -0,0 +1,3 @@ +export function rand(): number { + return Math.random() +} diff --git a/skills/leon/age/src/actions/run.ts b/skills/leon/age/src/actions/run.ts index a5b27972..743ac3d5 100644 --- a/skills/leon/age/src/actions/run.ts +++ b/skills/leon/age/src/actions/run.ts @@ -1,3 +1,5 @@ +import { rand } from '@sdk/testo' + export function run(): string { - return 'hello' + return `hello ${rand()}` } diff --git a/tsconfig.json b/tsconfig.json index bff82afe..c047cd49 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,8 @@ "baseUrl": ".", "paths": { "@@/*": ["./*"], - "@/*": ["./server/src/*"] + "@/*": ["./server/src/*"], + "@sdk/*": ["./bridges/nodejs/src/sdk/*"] }, "ignoreDeprecations": "5.0", "allowJs": true, @@ -20,5 +21,5 @@ }, "files": ["server/src/global.d.ts"], "include": ["server/src/**/*"], - "exclude": ["node_modules", "server/dist", "bridges/python", "tcp_server"] + "exclude": ["node_modules", "server/dist", "bridges", "tcp_server"] } From a836a7cc8c66082c9b326acb2d65744a35c4b8a6 Mon Sep 17 00:00:00 2001 From: louistiti Date: Wed, 3 May 2023 09:27:23 +0800 Subject: [PATCH 03/19] feat(bridge/nodejs): resolve SDK mapping --- scripts/build-binaries.js | 2 ++ skills/tsconfig.json | 8 ++++++++ tsconfig.json | 3 +-- 3 files changed, 11 insertions(+), 2 deletions(-) create mode 100644 skills/tsconfig.json diff --git a/scripts/build-binaries.js b/scripts/build-binaries.js index 1b8b8319..587fde98 100644 --- a/scripts/build-binaries.js +++ b/scripts/build-binaries.js @@ -149,6 +149,8 @@ BUILD_TARGETS.set('tcp-server', { NODEJS_BRIDGE_BIN_NAME ) + await fs.promises.rm(buildPath, { recursive: true, force: true }) + await command(`tsc --project ${tsconfigPath}`, { shell: true, stdio: 'inherit' diff --git a/skills/tsconfig.json b/skills/tsconfig.json new file mode 100644 index 00000000..34b397eb --- /dev/null +++ b/skills/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "@tsconfig/node16-strictest/tsconfig.json", + "compilerOptions": { + "paths": { + "@sdk/*": ["../bridges/nodejs/src/sdk/*"] + } + } +} diff --git a/tsconfig.json b/tsconfig.json index c047cd49..663be09b 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,8 +6,7 @@ "baseUrl": ".", "paths": { "@@/*": ["./*"], - "@/*": ["./server/src/*"], - "@sdk/*": ["./bridges/nodejs/src/sdk/*"] + "@/*": ["./server/src/*"] }, "ignoreDeprecations": "5.0", "allowJs": true, From 759e67ac4087d63876c36cde32a554cae3457a17 Mon Sep 17 00:00:00 2001 From: louistiti Date: Wed, 3 May 2023 16:02:57 +0800 Subject: [PATCH 04/19] fix(bridge/nodejs): SDK mapping --- bridges/nodejs/tsconfig.json | 5 +++++ tsconfig.json | 3 ++- 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/bridges/nodejs/tsconfig.json b/bridges/nodejs/tsconfig.json index 61c5397a..16617958 100644 --- a/bridges/nodejs/tsconfig.json +++ b/bridges/nodejs/tsconfig.json @@ -5,6 +5,11 @@ "outDir": "./dist/bin", "baseUrl": "." }, + "ts-node": { + "swc": true, + "require": ["tsconfig-paths/register"], + "files": true + }, "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } diff --git a/tsconfig.json b/tsconfig.json index 663be09b..c047cd49 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -6,7 +6,8 @@ "baseUrl": ".", "paths": { "@@/*": ["./*"], - "@/*": ["./server/src/*"] + "@/*": ["./server/src/*"], + "@sdk/*": ["./bridges/nodejs/src/sdk/*"] }, "ignoreDeprecations": "5.0", "allowJs": true, From 8349e4120510bc19cedd9e0f48d60290ecb228bc Mon Sep 17 00:00:00 2001 From: louistiti Date: Wed, 3 May 2023 23:28:12 +0800 Subject: [PATCH 05/19] feat(bridge/nodejs): answers --- bridges/nodejs/src/main.ts | 98 +++++++++++------------------- bridges/nodejs/src/sdk/answer.ts | 84 +++++++++++++++++++++++++ bridges/nodejs/src/sdk/testo.ts | 3 - bridges/nodejs/src/sdk/types.ts | 3 + bridges/nodejs/src/types.ts | 28 +++++++++ bridges/nodejs/src/utils.ts | 17 ++++++ bridges/nodejs/tsconfig.json | 5 +- skills/leon/age/src/actions/run.ts | 9 ++- skills/tsconfig.json | 1 + tsconfig.json | 1 + 10 files changed, 180 insertions(+), 69 deletions(-) create mode 100644 bridges/nodejs/src/sdk/answer.ts delete mode 100644 bridges/nodejs/src/sdk/testo.ts create mode 100644 bridges/nodejs/src/sdk/types.ts create mode 100644 bridges/nodejs/src/types.ts create mode 100644 bridges/nodejs/src/utils.ts diff --git a/bridges/nodejs/src/main.ts b/bridges/nodejs/src/main.ts index 4a3747aa..67d90328 100644 --- a/bridges/nodejs/src/main.ts +++ b/bridges/nodejs/src/main.ts @@ -1,71 +1,45 @@ -import fs from 'node:fs' import path from 'node:path' -const { - argv: [, , INTENT_OBJ_FILE_PATH] -} = process - +import { getIntentObject } from '@bridge/utils' ;(async (): Promise => { - if (INTENT_OBJ_FILE_PATH) { - const { - domain, - skill, - action, - lang, - utterance, - current_entities: currentEntities, - entities, - current_resolvers: currentResolvers, - resolvers, - slots - } = JSON.parse(await fs.promises.readFile(INTENT_OBJ_FILE_PATH, 'utf8')) + const { + domain, + skill, + action, + lang, + utterance, + current_entities: currentEntities, + entities, + current_resolvers: currentResolvers, + resolvers, + slots + } = await getIntentObject() - const params = { - lang, - utterance, - currentEntities, - entities, - currentResolvers, - resolvers, - slots - } + const params = { + lang, + utterance, + currentEntities, + entities, + currentResolvers, + resolvers, + slots + } - try { - const { [action]: actionFunction } = await import( - path.join( - process.cwd(), - 'skills', - domain, - skill, - 'src', - 'actions', - `${action}.ts` - ) + try { + const { [action]: actionFunction } = await import( + path.join( + process.cwd(), + 'skills', + domain, + skill, + 'src', + 'actions', + `${action}.ts` ) + ) - const speech = actionFunction(params) - - console.log( - JSON.stringify({ - domain, - skill, - action, - lang, - utterance, - entities, - slots, - // TODO - output: { - type: 'end', - codes: '', - speech, - core: {}, - options: {} - } - }) - ) - } catch (e) { - console.error('Error while running action:', e) - } + actionFunction(params) + } catch (e) { + console.error('Error while running action:', e) } })() diff --git a/bridges/nodejs/src/sdk/answer.ts b/bridges/nodejs/src/sdk/answer.ts new file mode 100644 index 00000000..e03f28c5 --- /dev/null +++ b/bridges/nodejs/src/sdk/answer.ts @@ -0,0 +1,84 @@ +import type { ActionResponse } from '@bridge/sdk/types' +import { getIntentObject } from '@bridge/utils' +import { AnswerTypes } from '@bridge/types' + +/** + * Holds methods to communicate data from the skill to the core + */ + +abstract class Answer { + /** + * Send an answer to the core + * @param text + */ + protected abstract text(text: string): Promise + + /** + * Create an answer object to send an answer to the core + * @param type The type of the answer + * @param text The text to send + */ + protected async createAnswerObject( + type: AnswerTypes, + text: string + ): Promise { + try { + const answer = { + ...(await getIntentObject()), + output: { + type, + codes: '', // TODO + speech: text, + core: {}, // TODO + options: {} // TODO + } + } + + process.stdout.write(JSON.stringify(answer)) + + return answer + } catch (e) { + console.error('Error creating answer object:', e) + + return null + } + } +} + +export class IntermediateAnswer extends Answer { + /** + * Create an answer object with the intermediate type + * to send an intermediate answer to the core. + * Used to send an answer before the final answer + * @param text The text to send + * @example await new IntermediateAnswer().text('intermediate answer') + */ + public async text(text: string): Promise { + try { + return await this.createAnswerObject(AnswerTypes.Intermediate, text) + } catch (e) { + console.error('Error creating intermediate answer:', e) + + return null + } + } +} + +export class FinalAnswer extends Answer { + /** + * Create an answer object with the final type + * to send a final answer to the core. + * Used to send an answer before the end of the skill action + * @param text The text to send + * @example await new FinalAnswer().text('final answer') + */ + public async text(text: string): Promise { + try { + return await this.createAnswerObject(AnswerTypes.Final, text) + } catch (e) { + console.error('Error creating final answer:', e) + + return null + } + } +} diff --git a/bridges/nodejs/src/sdk/testo.ts b/bridges/nodejs/src/sdk/testo.ts deleted file mode 100644 index 32f20dcb..00000000 --- a/bridges/nodejs/src/sdk/testo.ts +++ /dev/null @@ -1,3 +0,0 @@ -export function rand(): number { - return Math.random() -} diff --git a/bridges/nodejs/src/sdk/types.ts b/bridges/nodejs/src/sdk/types.ts new file mode 100644 index 00000000..cf3aacc7 --- /dev/null +++ b/bridges/nodejs/src/sdk/types.ts @@ -0,0 +1,3 @@ +import type { AnswerObject } from '@bridge/types' + +export type ActionResponse = AnswerObject | null diff --git a/bridges/nodejs/src/types.ts b/bridges/nodejs/src/types.ts new file mode 100644 index 00000000..31d5281a --- /dev/null +++ b/bridges/nodejs/src/types.ts @@ -0,0 +1,28 @@ +export enum AnswerTypes { + Intermediate = 'inter', + Final = 'end' +} + +export interface IntentObject { + id: string + domain: string + skill: string + action: string + lang: string + utterance: string + current_entities: unknown[] // TODO + entities: unknown[] // TODO + current_resolvers: unknown[] // TODO + resolvers: unknown[] // TODO + slots: Record[] // TODO +} + +export interface AnswerObject extends IntentObject { + output: { + type: AnswerTypes + codes: string + speech: string + core: unknown // TODO + options: unknown // TODO + } +} diff --git a/bridges/nodejs/src/utils.ts b/bridges/nodejs/src/utils.ts new file mode 100644 index 00000000..a0bcd7a2 --- /dev/null +++ b/bridges/nodejs/src/utils.ts @@ -0,0 +1,17 @@ +import fs from 'node:fs' + +import type { IntentObject } from '@bridge/types' + +const { + argv: [, , INTENT_OBJ_FILE_PATH] +} = process + +/** + * Get the intent object from the temporary intent file + * @example await getIntentObject() // { ... } + */ +export async function getIntentObject(): Promise { + return JSON.parse( + await fs.promises.readFile(INTENT_OBJ_FILE_PATH as string, 'utf8') + ) +} diff --git a/bridges/nodejs/tsconfig.json b/bridges/nodejs/tsconfig.json index 16617958..27e1eda0 100644 --- a/bridges/nodejs/tsconfig.json +++ b/bridges/nodejs/tsconfig.json @@ -3,7 +3,10 @@ "compilerOptions": { "rootDir": "./src", "outDir": "./dist/bin", - "baseUrl": "." + "baseUrl": ".", + "paths": { + "@bridge/*": ["./src/*"] + } }, "ts-node": { "swc": true, diff --git a/skills/leon/age/src/actions/run.ts b/skills/leon/age/src/actions/run.ts index 743ac3d5..5b38ee39 100644 --- a/skills/leon/age/src/actions/run.ts +++ b/skills/leon/age/src/actions/run.ts @@ -1,5 +1,8 @@ -import { rand } from '@sdk/testo' +import type { ActionResponse } from '@sdk/types' +import { IntermediateAnswer, FinalAnswer } from '@sdk/answer' -export function run(): string { - return `hello ${rand()}` +export async function run(): Promise { + await new IntermediateAnswer().text('intermediate answer') + + return await new FinalAnswer().text('final answer') } diff --git a/skills/tsconfig.json b/skills/tsconfig.json index 34b397eb..42f752e7 100644 --- a/skills/tsconfig.json +++ b/skills/tsconfig.json @@ -2,6 +2,7 @@ "extends": "@tsconfig/node16-strictest/tsconfig.json", "compilerOptions": { "paths": { + "@bridge/*": ["../bridges/nodejs/src/*"], "@sdk/*": ["../bridges/nodejs/src/sdk/*"] } } diff --git a/tsconfig.json b/tsconfig.json index c047cd49..c7e79259 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -7,6 +7,7 @@ "paths": { "@@/*": ["./*"], "@/*": ["./server/src/*"], + "@bridge/*": ["./bridges/nodejs/src/*"], "@sdk/*": ["./bridges/nodejs/src/sdk/*"] }, "ignoreDeprecations": "5.0", From c9678fe34f4f8b7123d67eac136ac258209c4fa8 Mon Sep 17 00:00:00 2001 From: louistiti Date: Thu, 4 May 2023 21:40:11 +0800 Subject: [PATCH 06/19] refactor(bridge/python): flush standard output every time --- bridges/nodejs/src/main.ts | 2 +- bridges/python/src/utils.py | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/bridges/nodejs/src/main.ts b/bridges/nodejs/src/main.ts index 67d90328..3f2f0dcb 100644 --- a/bridges/nodejs/src/main.ts +++ b/bridges/nodejs/src/main.ts @@ -38,7 +38,7 @@ import { getIntentObject } from '@bridge/utils' ) ) - actionFunction(params) + await actionFunction(params) } catch (e) { console.error('Error while running action:', e) } diff --git a/bridges/python/src/utils.py b/bridges/python/src/utils.py index 4ba52df8..44c680f8 100644 --- a/bridges/python/src/utils.py +++ b/bridges/python/src/utils.py @@ -90,8 +90,7 @@ def output(type, content = '', core = { }): } })) - if (type == 'inter'): - sys.stdout.flush() + sys.stdout.flush() def http(method, url, headers = None): """Send HTTP request with the Leon user agent""" From e42655a5ffd2796d6f394d2c9a738fddc9721241 Mon Sep 17 00:00:00 2001 From: louistiti Date: Thu, 4 May 2023 22:34:29 +0800 Subject: [PATCH 07/19] refactor(server): remove skill answer type complexity --- server/src/core/brain/brain.ts | 73 +++++++++++++--------------------- server/src/core/brain/types.ts | 5 --- 2 files changed, 28 insertions(+), 50 deletions(-) diff --git a/server/src/core/brain/brain.ts b/server/src/core/brain/brain.ts index 44a11a1f..4cdee1b0 100644 --- a/server/src/core/brain/brain.ts +++ b/server/src/core/brain/brain.ts @@ -15,11 +15,7 @@ import type { IntentObject, SkillResult } from '@/core/brain/types' -import { - SkillActionTypes, - SkillBridges, - SkillOutputTypes -} from '@/core/brain/types' +import { SkillActionTypes, SkillBridges } from '@/core/brain/types' import { langs } from '@@/core/langs.json' import { HAS_TTS, @@ -199,18 +195,15 @@ export default class Brain { const obj = JSON.parse(data.toString()) if (typeof obj === 'object') { - if (obj.output.type === SkillOutputTypes.Intermediate) { - LogHelper.title(`${this.skillFriendlyName} skill`) - LogHelper.info(data.toString()) + LogHelper.title(`${this.skillFriendlyName} skill (on data)`) + LogHelper.info(data.toString()) - const speech = obj.output.speech.toString() - if (!this.isMuted) { - this.talk(speech) - } - this.speeches.push(speech) - } else { - this.skillOutput = data.toString() + const speech = obj.output.speech.toString() + if (!this.isMuted) { + this.talk(speech) } + this.speeches.push(speech) + this.skillOutput = data.toString() return Promise.resolve(null) } else { @@ -388,7 +381,7 @@ export default class Brain { // Catch the end of the skill execution this.skillProcess?.stdout.on('end', () => { - LogHelper.title(`${this.skillFriendlyName} skill`) + LogHelper.title(`${this.skillFriendlyName} skill (on end)`) LogHelper.info(this.skillOutput) let skillResult: SkillResult | undefined = undefined @@ -398,36 +391,26 @@ export default class Brain { try { skillResult = JSON.parse(this.skillOutput) - if (skillResult?.output.speech) { - skillResult.output.speech = - skillResult.output.speech.toString() - if (!this.isMuted) { - this.talk(skillResult.output.speech, true) - } - speeches.push(skillResult.output.speech) + // Synchronize the downloaded content if enabled + if ( + skillResult && + skillResult.output.options['synchronization'] && + skillResult.output.options['synchronization'].enabled && + skillResult.output.options['synchronization'].enabled === true + ) { + const sync = new Synchronizer( + this, + nluResult.classification, + skillResult.output.options['synchronization'] + ) - // Synchronize the downloaded content if enabled - if ( - skillResult.output.type === SkillOutputTypes.End && - skillResult.output.options['synchronization'] && - skillResult.output.options['synchronization'].enabled && - skillResult.output.options['synchronization'].enabled === - 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) - }) - } + // When the synchronization is finished + sync.synchronize((speech: string) => { + if (!this.isMuted) { + this.talk(speech) + } + speeches.push(speech) + }) } } catch (e) { LogHelper.title(`${this.skillFriendlyName} skill`) diff --git a/server/src/core/brain/types.ts b/server/src/core/brain/types.ts index 4bf0ced1..9aa96382 100644 --- a/server/src/core/brain/types.ts +++ b/server/src/core/brain/types.ts @@ -28,7 +28,6 @@ export interface SkillResult { entities: NEREntity[] slots: NLUSlots output: { - type: SkillOutputTypes codes: string[] speech: string core: SkillCoreData | undefined @@ -41,10 +40,6 @@ export enum SkillBridges { Python = 'python', NodeJS = 'nodejs' } -export enum SkillOutputTypes { - Intermediate = 'inter', - End = 'end' -} export enum SkillActionTypes { Logic = 'logic', Dialog = 'dialog' From 89a6c04a1e1665c971bf57b3b92e3ec3260f6550 Mon Sep 17 00:00:00 2001 From: louistiti Date: Fri, 5 May 2023 00:34:16 +0800 Subject: [PATCH 08/19] refactor(bridge/nodejs): new answer types (WIP) --- bridges/nodejs/src/sdk/answer.ts | 89 +++++++++---------------- bridges/nodejs/src/sdk/aurora/button.ts | 6 ++ bridges/nodejs/src/sdk/aurora/index.ts | 5 ++ bridges/nodejs/src/sdk/leon.ts | 47 +++++++++++++ bridges/nodejs/src/types.ts | 6 -- skills/leon/age/src/actions/run.ts | 13 ++-- 6 files changed, 96 insertions(+), 70 deletions(-) create mode 100644 bridges/nodejs/src/sdk/aurora/button.ts create mode 100644 bridges/nodejs/src/sdk/aurora/index.ts create mode 100644 bridges/nodejs/src/sdk/leon.ts diff --git a/bridges/nodejs/src/sdk/answer.ts b/bridges/nodejs/src/sdk/answer.ts index e03f28c5..1f3be58b 100644 --- a/bridges/nodejs/src/sdk/answer.ts +++ b/bridges/nodejs/src/sdk/answer.ts @@ -1,84 +1,55 @@ -import type { ActionResponse } from '@bridge/sdk/types' -import { getIntentObject } from '@bridge/utils' -import { AnswerTypes } from '@bridge/types' +import type { AnswerObject } from '@bridge/types' /** * Holds methods to communicate data from the skill to the core */ -abstract class Answer { - /** - * Send an answer to the core - * @param text - */ - protected abstract text(text: string): Promise +interface AnswerOptions { + text: string +} + +export abstract class Answer implements AnswerOptions { + public abstract text: string /** * Create an answer object to send an answer to the core * @param type The type of the answer * @param text The text to send */ - protected async createAnswerObject( - type: AnswerTypes, - text: string - ): Promise { - try { - const answer = { - ...(await getIntentObject()), - output: { - type, - codes: '', // TODO - speech: text, - core: {}, // TODO - options: {} // TODO - } - } - - process.stdout.write(JSON.stringify(answer)) - - return answer - } catch (e) { - console.error('Error creating answer object:', e) - - return null + public async createAnswerOutput(): Promise { + return { + codes: '', // TODO + speech: this.text, + core: {}, // TODO + options: {} // TODO } } } -export class IntermediateAnswer extends Answer { +export class TextAnswer extends Answer { + public override text: string + /** - * Create an answer object with the intermediate type - * to send an intermediate answer to the core. - * Used to send an answer before the final answer + * Create an answer object to send an answer containing text to the core * @param text The text to send - * @example await new IntermediateAnswer().text('intermediate answer') + * @example new TextAnswer('Hello world') */ - public async text(text: string): Promise { - try { - return await this.createAnswerObject(AnswerTypes.Intermediate, text) - } catch (e) { - console.error('Error creating intermediate answer:', e) - - return null - } + public constructor(text: string) { + super() + this.text = text } } -export class FinalAnswer extends Answer { +export class HTMLAnswer extends Answer { + public override html: string + /** - * Create an answer object with the final type - * to send a final answer to the core. - * Used to send an answer before the end of the skill action - * @param text The text to send - * @example await new FinalAnswer().text('final answer') + * Create an answer object to send an answer containing HTML to the core + * @param html The HTML to send + * @example new HTMLAnswer('
  • Apples
  • Strawberries
') */ - public async text(text: string): Promise { - try { - return await this.createAnswerObject(AnswerTypes.Final, text) - } catch (e) { - console.error('Error creating final answer:', e) - - return null - } + public constructor(html: string) { + super() + this.html = html } } diff --git a/bridges/nodejs/src/sdk/aurora/button.ts b/bridges/nodejs/src/sdk/aurora/button.ts new file mode 100644 index 00000000..3f7b92e0 --- /dev/null +++ b/bridges/nodejs/src/sdk/aurora/button.ts @@ -0,0 +1,6 @@ +// TODO: contains the button API. rendering engine <-> SDK +export class Button { + constructor() { + console.log('Button constructor') + } +} diff --git a/bridges/nodejs/src/sdk/aurora/index.ts b/bridges/nodejs/src/sdk/aurora/index.ts new file mode 100644 index 00000000..ae8121b2 --- /dev/null +++ b/bridges/nodejs/src/sdk/aurora/index.ts @@ -0,0 +1,5 @@ +import { Button } from './button' + +export default { + Button +} diff --git a/bridges/nodejs/src/sdk/leon.ts b/bridges/nodejs/src/sdk/leon.ts new file mode 100644 index 00000000..b7d3e382 --- /dev/null +++ b/bridges/nodejs/src/sdk/leon.ts @@ -0,0 +1,47 @@ +import fs from 'node:fs' + +import type { AnswerObject, IntentObject } from '@bridge/types' +import type { Answer } from '@sdk/answer' + +class Leon { + private static instance: Leon + + constructor() { + if (!Leon.instance) { + Leon.instance = this + } + } + + /** + * Send an answer to the core + * @param answer + */ + public async answer(answer: Answer): Promise { + try { + const answerObject: AnswerObject = { + ...(await this.getIntentObject()), + output: await answer.createAnswerOutput() + } + + process.stdout.write(JSON.stringify(answerObject)) + } catch (error) { + console.error('Error while creating answer:', error) + } + } + + /** + * Get the intent object from the temporary intent file + * @example await getIntentObject() // { ... } + */ + private async getIntentObject(): Promise { + const { + argv: [, , INTENT_OBJ_FILE_PATH] + } = process + + return JSON.parse( + await fs.promises.readFile(INTENT_OBJ_FILE_PATH as string, 'utf8') + ) + } +} + +export const leon = new Leon() diff --git a/bridges/nodejs/src/types.ts b/bridges/nodejs/src/types.ts index 31d5281a..d2e086ab 100644 --- a/bridges/nodejs/src/types.ts +++ b/bridges/nodejs/src/types.ts @@ -1,8 +1,3 @@ -export enum AnswerTypes { - Intermediate = 'inter', - Final = 'end' -} - export interface IntentObject { id: string domain: string @@ -19,7 +14,6 @@ export interface IntentObject { export interface AnswerObject extends IntentObject { output: { - type: AnswerTypes codes: string speech: string core: unknown // TODO diff --git a/skills/leon/age/src/actions/run.ts b/skills/leon/age/src/actions/run.ts index 5b38ee39..e86839e0 100644 --- a/skills/leon/age/src/actions/run.ts +++ b/skills/leon/age/src/actions/run.ts @@ -1,8 +1,11 @@ -import type { ActionResponse } from '@sdk/types' -import { IntermediateAnswer, FinalAnswer } from '@sdk/answer' +import { leon } from '@sdk/leon' +import { TextAnswer } from '@sdk/answer' +import { Button } from '@sdk/aurora/button' -export async function run(): Promise { - await new IntermediateAnswer().text('intermediate answer') +export async function run(): Promise { + await leon.answer(new TextAnswer('intermediate answer')) - return await new FinalAnswer().text('final answer') + console.log('button', Button) + + await leon.answer(new TextAnswer('final answer')) } From b65cfb70d660f65c7d94960cf45cada08c2703be Mon Sep 17 00:00:00 2001 From: louistiti Date: Fri, 5 May 2023 21:21:27 +0800 Subject: [PATCH 09/19] feat(bridge/nodejs): final answer bridge --- bridges/nodejs/src/constants.ts | 36 +++++++ bridges/nodejs/src/main.ts | 6 +- bridges/nodejs/src/sdk/answer.ts | 55 ----------- bridges/nodejs/src/sdk/leon.ts | 145 ++++++++++++++++++++++++----- bridges/nodejs/src/sdk/types.ts | 3 - bridges/nodejs/src/types.ts | 22 ----- bridges/nodejs/src/utils.ts | 17 ---- bridges/python/src/utils.py | 3 +- skills/leon/age/config/en.json | 3 +- skills/leon/age/src/actions/run.ts | 12 ++- 10 files changed, 175 insertions(+), 127 deletions(-) create mode 100644 bridges/nodejs/src/constants.ts delete mode 100644 bridges/nodejs/src/sdk/answer.ts delete mode 100644 bridges/nodejs/src/sdk/types.ts delete mode 100644 bridges/nodejs/src/types.ts delete mode 100644 bridges/nodejs/src/utils.ts diff --git a/bridges/nodejs/src/constants.ts b/bridges/nodejs/src/constants.ts new file mode 100644 index 00000000..874f6b12 --- /dev/null +++ b/bridges/nodejs/src/constants.ts @@ -0,0 +1,36 @@ +import fs from 'node:fs' +import path from 'node:path' + +const { + argv: [, , INTENT_OBJ_FILE_PATH] +} = process + +export const INTENT_OBJECT = JSON.parse( + fs.readFileSync(INTENT_OBJ_FILE_PATH as string, 'utf8') +) +export const SKILL_CONFIG = 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 = JSON.parse( + fs.readFileSync( + path.join( + process.cwd(), + 'skills', + INTENT_OBJECT.domain, + INTENT_OBJECT.skill, + 'src', + 'config.json' + ), + 'utf8' + ) +).configurations diff --git a/bridges/nodejs/src/main.ts b/bridges/nodejs/src/main.ts index 3f2f0dcb..9c6a1820 100644 --- a/bridges/nodejs/src/main.ts +++ b/bridges/nodejs/src/main.ts @@ -1,6 +1,6 @@ import path from 'node:path' -import { getIntentObject } from '@bridge/utils' +import { INTENT_OBJECT } from '@bridge/constants' ;(async (): Promise => { const { domain, @@ -13,7 +13,7 @@ import { getIntentObject } from '@bridge/utils' current_resolvers: currentResolvers, resolvers, slots - } = await getIntentObject() + } = INTENT_OBJECT const params = { lang, @@ -40,6 +40,6 @@ import { getIntentObject } from '@bridge/utils' await actionFunction(params) } catch (e) { - console.error('Error while running action:', e) + console.error(`Error while running "${skill}" skill "${action}" action:`, e) } })() diff --git a/bridges/nodejs/src/sdk/answer.ts b/bridges/nodejs/src/sdk/answer.ts deleted file mode 100644 index 1f3be58b..00000000 --- a/bridges/nodejs/src/sdk/answer.ts +++ /dev/null @@ -1,55 +0,0 @@ -import type { AnswerObject } from '@bridge/types' - -/** - * Holds methods to communicate data from the skill to the core - */ - -interface AnswerOptions { - text: string -} - -export abstract class Answer implements AnswerOptions { - public abstract text: string - - /** - * Create an answer object to send an answer to the core - * @param type The type of the answer - * @param text The text to send - */ - public async createAnswerOutput(): Promise { - return { - codes: '', // TODO - speech: this.text, - core: {}, // TODO - options: {} // TODO - } - } -} - -export class TextAnswer extends Answer { - public override text: string - - /** - * Create an answer object to send an answer containing text to the core - * @param text The text to send - * @example new TextAnswer('Hello world') - */ - public constructor(text: string) { - super() - this.text = text - } -} - -export class HTMLAnswer extends Answer { - public override html: string - - /** - * Create an answer object to send an answer containing HTML to the core - * @param html The HTML to send - * @example new HTMLAnswer('
  • Apples
  • Strawberries
') - */ - public constructor(html: string) { - super() - this.html = html - } -} diff --git a/bridges/nodejs/src/sdk/leon.ts b/bridges/nodejs/src/sdk/leon.ts index b7d3e382..5f13f3f0 100644 --- a/bridges/nodejs/src/sdk/leon.ts +++ b/bridges/nodejs/src/sdk/leon.ts @@ -1,7 +1,50 @@ -import fs from 'node:fs' +import { + INTENT_OBJECT, + SKILL_CONFIG, + SKILL_SRC_CONFIG +} from '@bridge/constants' -import type { AnswerObject, IntentObject } from '@bridge/types' -import type { Answer } from '@sdk/answer' +interface IntentObject { + id: string + domain: string + skill: string + action: string + lang: string + utterance: string + current_entities: unknown[] // TODO + entities: unknown[] // TODO + current_resolvers: unknown[] // TODO + resolvers: unknown[] // TODO + slots: Record[] // TODO +} +interface AnswerOutput extends IntentObject { + output: { + code: string + speech: string + core: AnswerCoreData + // eslint-disable-next-line @typescript-eslint/no-explicit-any + options: Record + } +} +interface AnswerCoreData { + restart?: boolean + isInActionLoop?: boolean + showNextActionSuggestions?: boolean + showSuggestions?: boolean +} +interface TextAnswer { + key: string + data?: AnswerData + core?: AnswerCoreData +} +interface WidgetAnswer { + // TODO + key: 'widget' + data?: AnswerData + core?: AnswerCoreData +} +type AnswerData = Record | null +type AnswerInput = TextAnswer | WidgetAnswer class Leon { private static instance: Leon @@ -13,34 +56,94 @@ class Leon { } /** - * Send an answer to the core - * @param answer + * Get source configuration + * @example getSRCConfig() // { credentials: { apiKey: 'abc' } } */ - public async answer(answer: Answer): Promise { + // eslint-disable-next-line @typescript-eslint/no-explicit-any + public getSRCConfig(key?: string): Record { try { - const answerObject: AnswerObject = { - ...(await this.getIntentObject()), - output: await answer.createAnswerOutput() + if (key) { + return SKILL_SRC_CONFIG[key] } - process.stdout.write(JSON.stringify(answerObject)) - } catch (error) { - console.error('Error while creating answer:', error) + return SKILL_SRC_CONFIG + } catch (e) { + console.error('Error while getting source configuration:', e) + + return {} } } /** - * Get the intent object from the temporary intent file - * @example await getIntentObject() // { ... } + * Apply data to the answer + * @param answerKey The answer key + * @param data The data to apply + * @example setAnswerData('key', { name: 'Leon' }) */ - private async getIntentObject(): Promise { - const { - argv: [, , INTENT_OBJ_FILE_PATH] - } = process + public setAnswerData( + answerKey: string, + data: AnswerData = null + ): string | null { + try { + // In case the answer key is a raw answer + if (!SKILL_CONFIG.answers[answerKey]) { + return answerKey + } - return JSON.parse( - await fs.promises.readFile(INTENT_OBJ_FILE_PATH as string, 'utf8') - ) + const answers = SKILL_CONFIG.answers[answerKey] + let answer + + 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 { + 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) + } } } diff --git a/bridges/nodejs/src/sdk/types.ts b/bridges/nodejs/src/sdk/types.ts deleted file mode 100644 index cf3aacc7..00000000 --- a/bridges/nodejs/src/sdk/types.ts +++ /dev/null @@ -1,3 +0,0 @@ -import type { AnswerObject } from '@bridge/types' - -export type ActionResponse = AnswerObject | null diff --git a/bridges/nodejs/src/types.ts b/bridges/nodejs/src/types.ts deleted file mode 100644 index d2e086ab..00000000 --- a/bridges/nodejs/src/types.ts +++ /dev/null @@ -1,22 +0,0 @@ -export interface IntentObject { - id: string - domain: string - skill: string - action: string - lang: string - utterance: string - current_entities: unknown[] // TODO - entities: unknown[] // TODO - current_resolvers: unknown[] // TODO - resolvers: unknown[] // TODO - slots: Record[] // TODO -} - -export interface AnswerObject extends IntentObject { - output: { - codes: string - speech: string - core: unknown // TODO - options: unknown // TODO - } -} diff --git a/bridges/nodejs/src/utils.ts b/bridges/nodejs/src/utils.ts deleted file mode 100644 index a0bcd7a2..00000000 --- a/bridges/nodejs/src/utils.ts +++ /dev/null @@ -1,17 +0,0 @@ -import fs from 'node:fs' - -import type { IntentObject } from '@bridge/types' - -const { - argv: [, , INTENT_OBJ_FILE_PATH] -} = process - -/** - * Get the intent object from the temporary intent file - * @example await getIntentObject() // { ... } - */ -export async function getIntentObject(): Promise { - return JSON.parse( - await fs.promises.readFile(INTENT_OBJ_FILE_PATH as string, 'utf8') - ) -} diff --git a/bridges/python/src/utils.py b/bridges/python/src/utils.py index 44c680f8..acb12e7c 100644 --- a/bridges/python/src/utils.py +++ b/bridges/python/src/utils.py @@ -30,7 +30,7 @@ def translate(key, dict = { }): """Pickup the language file according to the cmd arg and return the value according to the params""" - # "Temporize" for the data buffer ouput on the core + # "Temporize" for the data buffer output on the core sleep(0.1) output = '' @@ -82,6 +82,7 @@ def output(type, content = '', core = { }): 'entities': intent_obj['entities'], 'slots': intent_obj['slots'], 'output': { + # TODO: remove type as it is not needed anymore 'type': type, 'codes': codes, 'speech': speech, diff --git a/skills/leon/age/config/en.json b/skills/leon/age/config/en.json index f57882bb..60022056 100644 --- a/skills/leon/age/config/en.json +++ b/skills/leon/age/config/en.json @@ -7,6 +7,7 @@ } }, "answers": { - "default": ["I'm..."] + "default": ["I'm..."], + "greet": ["Hey, just a try %name% again %name%", "Another try, hi"] } } diff --git a/skills/leon/age/src/actions/run.ts b/skills/leon/age/src/actions/run.ts index e86839e0..427f86c4 100644 --- a/skills/leon/age/src/actions/run.ts +++ b/skills/leon/age/src/actions/run.ts @@ -1,11 +1,15 @@ import { leon } from '@sdk/leon' -import { TextAnswer } from '@sdk/answer' import { Button } from '@sdk/aurora/button' export async function run(): Promise { - await leon.answer(new TextAnswer('intermediate answer')) + await leon.answer({ key: 'default' }) + + await leon.answer({ + key: 'greet', + data: { + name: 'Louis' + } + }) console.log('button', Button) - - await leon.answer(new TextAnswer('final answer')) } From 1625aaf3cd576d9d5a9d50b16105bc4e7aa31302 Mon Sep 17 00:00:00 2001 From: Divlo Date: Fri, 5 May 2023 20:00:51 +0200 Subject: [PATCH 10/19] fix(bridge/nodejs): typings improvements --- bridges/nodejs/src/constants.ts | 8 ++++-- bridges/nodejs/src/main.ts | 14 +++++----- bridges/nodejs/src/sdk/leon.ts | 41 +++++++++++++++++------------- bridges/nodejs/tsconfig.json | 13 +++++++--- scripts/build-binaries.js | 3 +++ skills/leon/age/src/actions/run.ts | 3 ++- 6 files changed, 52 insertions(+), 30 deletions(-) diff --git a/bridges/nodejs/src/constants.ts b/bridges/nodejs/src/constants.ts index 874f6b12..3e8c734d 100644 --- a/bridges/nodejs/src/constants.ts +++ b/bridges/nodejs/src/constants.ts @@ -1,14 +1,18 @@ import fs from 'node:fs' import path from 'node:path' +import type { SkillConfigSchema } from '@/schemas/skill-schemas' + +import type { IntentObject } from '@sdk/leon' + const { argv: [, , INTENT_OBJ_FILE_PATH] } = process -export const INTENT_OBJECT = JSON.parse( +export const INTENT_OBJECT: IntentObject = JSON.parse( fs.readFileSync(INTENT_OBJ_FILE_PATH as string, 'utf8') ) -export const SKILL_CONFIG = JSON.parse( +export const SKILL_CONFIG: SkillConfigSchema = JSON.parse( fs.readFileSync( path.join( process.cwd(), diff --git a/bridges/nodejs/src/main.ts b/bridges/nodejs/src/main.ts index 9c6a1820..ea2a4a64 100644 --- a/bridges/nodejs/src/main.ts +++ b/bridges/nodejs/src/main.ts @@ -1,6 +1,7 @@ import path from 'node:path' import { INTENT_OBJECT } from '@bridge/constants' +import type { ActionFunction, ActionParams } from '@sdk/leon' ;(async (): Promise => { const { domain, @@ -8,25 +9,25 @@ import { INTENT_OBJECT } from '@bridge/constants' action, lang, utterance, - current_entities: currentEntities, + current_entities, entities, - current_resolvers: currentResolvers, + current_resolvers, resolvers, slots } = INTENT_OBJECT - const params = { + const params: ActionParams = { lang, utterance, - currentEntities, + current_entities, entities, - currentResolvers, + current_resolvers, resolvers, slots } try { - const { [action]: actionFunction } = await import( + const actionModule = await import( path.join( process.cwd(), 'skills', @@ -37,6 +38,7 @@ import { INTENT_OBJECT } from '@bridge/constants' `${action}.ts` ) ) + const actionFunction: ActionFunction = actionModule[action] await actionFunction(params) } catch (e) { diff --git a/bridges/nodejs/src/sdk/leon.ts b/bridges/nodejs/src/sdk/leon.ts index 5f13f3f0..14527d75 100644 --- a/bridges/nodejs/src/sdk/leon.ts +++ b/bridges/nodejs/src/sdk/leon.ts @@ -4,11 +4,7 @@ import { SKILL_SRC_CONFIG } from '@bridge/constants' -interface IntentObject { - id: string - domain: string - skill: string - action: string +export interface ActionParams { lang: string utterance: string current_entities: unknown[] // TODO @@ -17,32 +13,44 @@ interface IntentObject { resolvers: unknown[] // TODO slots: Record[] // TODO } +export type ActionFunction = (params: ActionParams) => Promise + +export interface IntentObject extends ActionParams { + id: string + domain: string + skill: string + action: string +} + interface AnswerOutput extends IntentObject { output: { - code: string + codes: string speech: string - core: AnswerCoreData - // eslint-disable-next-line @typescript-eslint/no-explicit-any - options: Record + core?: AnswerCoreData + options: Record } } + interface AnswerCoreData { restart?: boolean isInActionLoop?: boolean showNextActionSuggestions?: boolean showSuggestions?: boolean } + interface TextAnswer { key: string data?: AnswerData core?: AnswerCoreData } + interface WidgetAnswer { // TODO key: 'widget' data?: AnswerData core?: AnswerCoreData } + type AnswerData = Record | null type AnswerInput = TextAnswer | WidgetAnswer @@ -59,8 +67,7 @@ class Leon { * Get source configuration * @example getSRCConfig() // { credentials: { apiKey: 'abc' } } */ - // eslint-disable-next-line @typescript-eslint/no-explicit-any - public getSRCConfig(key?: string): Record { + public getSRCConfig>(key?: string): T { try { if (key) { return SKILL_SRC_CONFIG[key] @@ -70,7 +77,7 @@ class Leon { } catch (e) { console.error('Error while getting source configuration:', e) - return {} + return {} as T } } @@ -86,15 +93,15 @@ class Leon { ): string | null { try { // In case the answer key is a raw answer - if (!SKILL_CONFIG.answers[answerKey]) { + if (SKILL_CONFIG.answers == null || !SKILL_CONFIG.answers[answerKey]) { return answerKey } - const answers = SKILL_CONFIG.answers[answerKey] - let answer + const answers = SKILL_CONFIG.answers[answerKey] ?? '' + let answer: string if (Array.isArray(answers)) { - answer = answers[Math.floor(Math.random() * answers.length)] + answer = answers[Math.floor(Math.random() * answers.length)] ?? '' } else { answer = answers } @@ -134,7 +141,7 @@ class Leon { ...INTENT_OBJECT, output: { codes: answerInput.key, - speech: this.setAnswerData(answerInput.key, answerInput.data), + speech: this.setAnswerData(answerInput.key, answerInput.data) ?? '', core: answerInput.core, options: this.getSRCConfig('options') } diff --git a/bridges/nodejs/tsconfig.json b/bridges/nodejs/tsconfig.json index 27e1eda0..bd36b961 100644 --- a/bridges/nodejs/tsconfig.json +++ b/bridges/nodejs/tsconfig.json @@ -1,18 +1,23 @@ { "extends": "../../tsconfig.json", "compilerOptions": { - "rootDir": "./src", "outDir": "./dist/bin", "baseUrl": ".", "paths": { - "@bridge/*": ["./src/*"] - } + "@@/*": ["../../*"], + "@/*": ["../../server/src/*"], + "@server/schemas/*": ["../../server/src/schemas/*"], + "@bridge/*": ["./src/*"], + "@sdk/*": ["./src/sdk/*"] + }, + "exactOptionalPropertyTypes": false, + "declaration": true }, "ts-node": { "swc": true, "require": ["tsconfig-paths/register"], "files": true }, - "include": ["src/**/*"], + "include": ["src/**/*", "../../server/src/schemas/**/*"], "exclude": ["node_modules", "dist"] } diff --git a/scripts/build-binaries.js b/scripts/build-binaries.js index 587fde98..c3f00ce8 100644 --- a/scripts/build-binaries.js +++ b/scripts/build-binaries.js @@ -141,6 +141,9 @@ BUILD_TARGETS.set('tcp-server', { const distMainFilePath = path.join( NODEJS_BRIDGE_DIST_PATH, 'bin', + 'bridges', + 'nodejs', + 'src', 'main.js' ) const distRenamedMainFilePath = path.join( diff --git a/skills/leon/age/src/actions/run.ts b/skills/leon/age/src/actions/run.ts index 427f86c4..983d1b72 100644 --- a/skills/leon/age/src/actions/run.ts +++ b/skills/leon/age/src/actions/run.ts @@ -1,7 +1,8 @@ import { leon } from '@sdk/leon' +import type { ActionFunction } from '@sdk/leon' import { Button } from '@sdk/aurora/button' -export async function run(): Promise { +export const run: ActionFunction = async () => { await leon.answer({ key: 'default' }) await leon.answer({ From 6b76e6a1026ef9fbb8ed5569f4010f98cd2496e9 Mon Sep 17 00:00:00 2001 From: Divlo Date: Fri, 5 May 2023 20:24:01 +0200 Subject: [PATCH 11/19] fix(bridge/nodejs): stricter leon.getSRCConfig types --- bridges/nodejs/src/constants.ts | 2 +- bridges/nodejs/src/sdk/leon.ts | 10 +++++++--- skills/leon/age/config/en.json | 3 ++- skills/leon/age/src/actions/run.ts | 12 ++++++++++++ 4 files changed, 22 insertions(+), 5 deletions(-) diff --git a/bridges/nodejs/src/constants.ts b/bridges/nodejs/src/constants.ts index 3e8c734d..c6fc1ec4 100644 --- a/bridges/nodejs/src/constants.ts +++ b/bridges/nodejs/src/constants.ts @@ -25,7 +25,7 @@ export const SKILL_CONFIG: SkillConfigSchema = JSON.parse( 'utf8' ) ) -export const SKILL_SRC_CONFIG = JSON.parse( +export const SKILL_SRC_CONFIG: Record = JSON.parse( fs.readFileSync( path.join( process.cwd(), diff --git a/bridges/nodejs/src/sdk/leon.ts b/bridges/nodejs/src/sdk/leon.ts index 14527d75..aee07991 100644 --- a/bridges/nodejs/src/sdk/leon.ts +++ b/bridges/nodejs/src/sdk/leon.ts @@ -67,13 +67,17 @@ class Leon { * Get source configuration * @example getSRCConfig() // { credentials: { apiKey: 'abc' } } */ - public getSRCConfig>(key?: string): T { + public getSRCConfig(key: string): T + public getSRCConfig>(key?: undefined): T + public getSRCConfig | unknown>( + key?: string + ): T { try { if (key) { - return SKILL_SRC_CONFIG[key] + return SKILL_SRC_CONFIG[key] as T } - return SKILL_SRC_CONFIG + return SKILL_SRC_CONFIG as T } catch (e) { console.error('Error while getting source configuration:', e) diff --git a/skills/leon/age/config/en.json b/skills/leon/age/config/en.json index 60022056..d31d8eb8 100644 --- a/skills/leon/age/config/en.json +++ b/skills/leon/age/config/en.json @@ -8,6 +8,7 @@ }, "answers": { "default": ["I'm..."], - "greet": ["Hey, just a try %name% again %name%", "Another try, hi"] + "greet": ["Hey, just a try %name% again %name%", "Another try, hi"], + "config": ["%config%"] } } diff --git a/skills/leon/age/src/actions/run.ts b/skills/leon/age/src/actions/run.ts index 983d1b72..584b9d3a 100644 --- a/skills/leon/age/src/actions/run.ts +++ b/skills/leon/age/src/actions/run.ts @@ -13,4 +13,16 @@ export const run: ActionFunction = async () => { }) console.log('button', Button) + + const { someSampleConfig } = leon.getSRCConfig<{ + options: { someSampleConfig: string } + }>()['options'] + + const options = leon.getSRCConfig<{ someSampleConfig: string }>('options') + await leon.answer({ + key: 'config', + data: { + config: options.someSampleConfig + someSampleConfig + } + }) } From e89b5b0ea3665b8f58961d18269d84b690d89253 Mon Sep 17 00:00:00 2001 From: louistiti Date: Sat, 6 May 2023 18:26:22 +0800 Subject: [PATCH 12/19] refactor(bridge/nodejs): isolate types from bridge --- bridges/nodejs/src/constants.ts | 2 +- bridges/nodejs/src/main.ts | 2 +- bridges/nodejs/src/sdk/leon.ts | 51 +---------------------------- bridges/nodejs/src/sdk/types.ts | 52 ++++++++++++++++++++++++++++++ skills/leon/age/src/actions/run.ts | 4 +-- 5 files changed, 57 insertions(+), 54 deletions(-) create mode 100644 bridges/nodejs/src/sdk/types.ts diff --git a/bridges/nodejs/src/constants.ts b/bridges/nodejs/src/constants.ts index c6fc1ec4..b5919673 100644 --- a/bridges/nodejs/src/constants.ts +++ b/bridges/nodejs/src/constants.ts @@ -3,7 +3,7 @@ import path from 'node:path' import type { SkillConfigSchema } from '@/schemas/skill-schemas' -import type { IntentObject } from '@sdk/leon' +import type { IntentObject } from '@sdk/types' const { argv: [, , INTENT_OBJ_FILE_PATH] diff --git a/bridges/nodejs/src/main.ts b/bridges/nodejs/src/main.ts index ea2a4a64..37e9772f 100644 --- a/bridges/nodejs/src/main.ts +++ b/bridges/nodejs/src/main.ts @@ -1,7 +1,7 @@ import path from 'node:path' +import type { ActionFunction, ActionParams } from '@sdk/types' import { INTENT_OBJECT } from '@bridge/constants' -import type { ActionFunction, ActionParams } from '@sdk/leon' ;(async (): Promise => { const { domain, diff --git a/bridges/nodejs/src/sdk/leon.ts b/bridges/nodejs/src/sdk/leon.ts index aee07991..0d2f26d4 100644 --- a/bridges/nodejs/src/sdk/leon.ts +++ b/bridges/nodejs/src/sdk/leon.ts @@ -1,59 +1,10 @@ +import type { AnswerData, AnswerInput, AnswerOutput } from '@sdk/types' import { INTENT_OBJECT, SKILL_CONFIG, SKILL_SRC_CONFIG } from '@bridge/constants' -export interface ActionParams { - lang: string - utterance: string - current_entities: unknown[] // TODO - entities: unknown[] // TODO - current_resolvers: unknown[] // TODO - resolvers: unknown[] // TODO - slots: Record[] // TODO -} -export type ActionFunction = (params: ActionParams) => Promise - -export interface IntentObject extends ActionParams { - id: string - domain: string - skill: string - action: string -} - -interface AnswerOutput extends IntentObject { - output: { - codes: string - speech: string - core?: AnswerCoreData - options: Record - } -} - -interface AnswerCoreData { - restart?: boolean - isInActionLoop?: boolean - showNextActionSuggestions?: boolean - showSuggestions?: boolean -} - -interface TextAnswer { - key: string - data?: AnswerData - core?: AnswerCoreData -} - -interface WidgetAnswer { - // TODO - key: 'widget' - data?: AnswerData - core?: AnswerCoreData -} - -type AnswerData = Record | null -type AnswerInput = TextAnswer | WidgetAnswer - class Leon { private static instance: Leon diff --git a/bridges/nodejs/src/sdk/types.ts b/bridges/nodejs/src/sdk/types.ts new file mode 100644 index 00000000..c53c6734 --- /dev/null +++ b/bridges/nodejs/src/sdk/types.ts @@ -0,0 +1,52 @@ +/** + * Action types + */ + +export interface ActionParams { + lang: string + utterance: string + current_entities: unknown[] // TODO + entities: unknown[] // TODO + current_resolvers: unknown[] // TODO + resolvers: unknown[] // TODO + slots: Record[] // TODO +} +export type ActionFunction = (params: ActionParams) => Promise + +export interface IntentObject extends ActionParams { + id: string + domain: string + skill: string + action: string +} + +/** + * Answer types + */ +export interface AnswerOutput extends IntentObject { + output: { + codes: string + speech: string + core?: AnswerCoreData + options: Record + } +} +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 | null +export type AnswerInput = TextAnswer | WidgetAnswer diff --git a/skills/leon/age/src/actions/run.ts b/skills/leon/age/src/actions/run.ts index 584b9d3a..6ec726c0 100644 --- a/skills/leon/age/src/actions/run.ts +++ b/skills/leon/age/src/actions/run.ts @@ -1,8 +1,8 @@ +import type { ActionFunction } from '@sdk/types' import { leon } from '@sdk/leon' -import type { ActionFunction } from '@sdk/leon' import { Button } from '@sdk/aurora/button' -export const run: ActionFunction = async () => { +export const run: ActionFunction = async function () { await leon.answer({ key: 'default' }) await leon.answer({ From 673cf0426fd02cdc0f5de5c5c6c552e262a0812e Mon Sep 17 00:00:00 2001 From: Divlo Date: Sat, 6 May 2023 13:55:43 +0200 Subject: [PATCH 13/19] fix(bridge/nodejs): build error --- bridges/nodejs/src/constants.ts | 2 +- bridges/nodejs/tsconfig.json | 4 ++-- server/src/helpers/system-helper.ts | 2 +- tsconfig.json | 3 ++- 4 files changed, 6 insertions(+), 5 deletions(-) diff --git a/bridges/nodejs/src/constants.ts b/bridges/nodejs/src/constants.ts index b5919673..96ac5a5d 100644 --- a/bridges/nodejs/src/constants.ts +++ b/bridges/nodejs/src/constants.ts @@ -1,7 +1,7 @@ import fs from 'node:fs' import path from 'node:path' -import type { SkillConfigSchema } from '@/schemas/skill-schemas' +import type { SkillConfigSchema } from '@server/schemas/skill-schemas' import type { IntentObject } from '@sdk/types' diff --git a/bridges/nodejs/tsconfig.json b/bridges/nodejs/tsconfig.json index bd36b961..3623143f 100644 --- a/bridges/nodejs/tsconfig.json +++ b/bridges/nodejs/tsconfig.json @@ -6,7 +6,7 @@ "paths": { "@@/*": ["../../*"], "@/*": ["../../server/src/*"], - "@server/schemas/*": ["../../server/src/schemas/*"], + "@server/*": ["../../server/src/*"], "@bridge/*": ["./src/*"], "@sdk/*": ["./src/sdk/*"] }, @@ -18,6 +18,6 @@ "require": ["tsconfig-paths/register"], "files": true }, - "include": ["src/**/*", "../../server/src/schemas/**/*"], + "include": ["src/**/*"], "exclude": ["node_modules", "dist"] } diff --git a/server/src/helpers/system-helper.ts b/server/src/helpers/system-helper.ts index 236bcdfe..e7df50f8 100644 --- a/server/src/helpers/system-helper.ts +++ b/server/src/helpers/system-helper.ts @@ -8,7 +8,7 @@ enum OSNames { Linux = 'Linux', Unknown = 'Unknown' } -enum BinaryFolderNames { +export enum BinaryFolderNames { Linux64Bit = 'linux-x86_64', // Linux 64-bit (Intel) LinuxARM64 = 'linux-aarch64', // Linux 64-bit (ARM) MacOS64Bit = 'macosx-x86_64', // Apple 64-bit (Intel) diff --git a/tsconfig.json b/tsconfig.json index c7e79259..aae96510 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -13,7 +13,8 @@ "ignoreDeprecations": "5.0", "allowJs": true, "checkJs": false, - "resolveJsonModule": true + "resolveJsonModule": true, + "declaration": true }, "ts-node": { "swc": true, From 2cb030f47b87faee642415f10c11ba76c388de69 Mon Sep 17 00:00:00 2001 From: Divlo Date: Sat, 6 May 2023 14:03:56 +0200 Subject: [PATCH 14/19] fix(bridge/nodejs): usage of types from server/core/brain/types --- bridges/nodejs/src/sdk/types.ts | 19 +++---------------- server/src/core/brain/types.ts | 13 ++++++++----- 2 files changed, 11 insertions(+), 21 deletions(-) diff --git a/bridges/nodejs/src/sdk/types.ts b/bridges/nodejs/src/sdk/types.ts index c53c6734..ad4df61e 100644 --- a/bridges/nodejs/src/sdk/types.ts +++ b/bridges/nodejs/src/sdk/types.ts @@ -1,25 +1,12 @@ /** * Action types */ +import type { ActionParams, IntentObject } from '@/core/brain/types' + +export type { ActionParams, IntentObject } -export interface ActionParams { - lang: string - utterance: string - current_entities: unknown[] // TODO - entities: unknown[] // TODO - current_resolvers: unknown[] // TODO - resolvers: unknown[] // TODO - slots: Record[] // TODO -} export type ActionFunction = (params: ActionParams) => Promise -export interface IntentObject extends ActionParams { - id: string - domain: string - skill: string - action: string -} - /** * Answer types */ diff --git a/server/src/core/brain/types.ts b/server/src/core/brain/types.ts index 9aa96382..9374fed0 100644 --- a/server/src/core/brain/types.ts +++ b/server/src/core/brain/types.ts @@ -45,12 +45,8 @@ export enum SkillActionTypes { Dialog = 'dialog' } -export interface IntentObject { - id: string +export interface ActionParams { lang: ShortLanguageCode - domain: NLPDomain - skill: NLPSkill - action: NLPAction utterance: NLPUtterance current_entities: NEREntity[] entities: NEREntity[] @@ -59,6 +55,13 @@ export interface IntentObject { slots: { [key: string]: NLUSlot['value'] | undefined } } +export interface IntentObject extends ActionParams { + id: string + domain: NLPDomain + skill: NLPSkill + action: NLPAction +} + export interface BrainProcessResult extends NLUResult { speeches: string[] executionTime: number From de08807f1ad6a8cf3320c92ed8495e258d0384b3 Mon Sep 17 00:00:00 2001 From: Divlo Date: Sat, 6 May 2023 14:21:24 +0200 Subject: [PATCH 15/19] feat(bridge/nodejs): add Network module (WIP) --- bridges/nodejs/package.json | 3 +++ bridges/nodejs/src/sdk/network.ts | 22 ++++++++++++++++++++++ skills/leon/age/config/en.json | 2 +- skills/leon/age/src/actions/run.ts | 16 ++++++++++++++-- 4 files changed, 40 insertions(+), 3 deletions(-) create mode 100644 bridges/nodejs/src/sdk/network.ts diff --git a/bridges/nodejs/package.json b/bridges/nodejs/package.json index 0264c619..675b87db 100644 --- a/bridges/nodejs/package.json +++ b/bridges/nodejs/package.json @@ -11,5 +11,8 @@ "homepage": "https://getleon.ai", "bugs": { "url": "https://github.com/leon-ai/leon/issues" + }, + "dependencies": { + "axios": "1.4.0" } } diff --git a/bridges/nodejs/src/sdk/network.ts b/bridges/nodejs/src/sdk/network.ts new file mode 100644 index 00000000..572a33c2 --- /dev/null +++ b/bridges/nodejs/src/sdk/network.ts @@ -0,0 +1,22 @@ +import axios from 'axios' +import type { AxiosInstance } from 'axios' + +export interface NetworkOptions { + baseURL?: string +} + +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 get(url: string): Promise { + return (await this.axios.get(url)).data as T + } +} diff --git a/skills/leon/age/config/en.json b/skills/leon/age/config/en.json index d31d8eb8..9341e8e8 100644 --- a/skills/leon/age/config/en.json +++ b/skills/leon/age/config/en.json @@ -9,6 +9,6 @@ "answers": { "default": ["I'm..."], "greet": ["Hey, just a try %name% again %name%", "Another try, hi"], - "config": ["%config%"] + "answer": ["%answer%"] } } diff --git a/skills/leon/age/src/actions/run.ts b/skills/leon/age/src/actions/run.ts index 6ec726c0..d9312b44 100644 --- a/skills/leon/age/src/actions/run.ts +++ b/skills/leon/age/src/actions/run.ts @@ -1,5 +1,6 @@ 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 () { @@ -20,9 +21,20 @@ export const run: ActionFunction = async function () { const options = leon.getSRCConfig<{ someSampleConfig: string }>('options') await leon.answer({ - key: 'config', + key: 'answer', data: { - config: options.someSampleConfig + someSampleConfig + answer: options.someSampleConfig + someSampleConfig + } + }) + + const network = new Network({ + baseURL: 'https://jsonplaceholder.typicode.com' + }) + const data = await network.get<{ title: string }>('/todos/1') + await leon.answer({ + key: 'answer', + data: { + answer: `Todo n°1: ${data.title}` } }) } From 07e253f663a7c253b8c4638fd3a73653af7471cb Mon Sep 17 00:00:00 2001 From: Divlo Date: Sat, 6 May 2023 15:38:49 +0200 Subject: [PATCH 16/19] build(bridge/nodejs): usage of ncc --- bridges/nodejs/tsconfig.json | 1 + package.json | 1 + scripts/build-binaries.js | 29 +++++++++++++++++++---------- 3 files changed, 21 insertions(+), 10 deletions(-) diff --git a/bridges/nodejs/tsconfig.json b/bridges/nodejs/tsconfig.json index 3623143f..fdb6b123 100644 --- a/bridges/nodejs/tsconfig.json +++ b/bridges/nodejs/tsconfig.json @@ -2,6 +2,7 @@ "extends": "../../tsconfig.json", "compilerOptions": { "outDir": "./dist/bin", + "rootDir": "../../", "baseUrl": ".", "paths": { "@@/*": ["../../*"], diff --git a/package.json b/package.json index 949cf3ca..9823d72c 100644 --- a/package.json +++ b/package.json @@ -117,6 +117,7 @@ "@types/node-wav": "0.0.0", "@typescript-eslint/eslint-plugin": "5.55.0", "@typescript-eslint/parser": "5.55.0", + "@vercel/ncc": "0.36.1", "cli-spinner": "0.2.10", "eslint": "8.22.0", "eslint-config-prettier": "8.5.0", diff --git a/scripts/build-binaries.js b/scripts/build-binaries.js index c3f00ce8..99edeb78 100644 --- a/scripts/build-binaries.js +++ b/scripts/build-binaries.js @@ -137,24 +137,33 @@ BUILD_TARGETS.set('tcp-server', { * Build for binaries not requiring a Python environment */ try { - const tsconfigPath = path.join(NODEJS_BRIDGE_ROOT_PATH, 'tsconfig.json') + const currentWorkingDirectory = process.cwd() + process.chdir(NODEJS_BRIDGE_ROOT_PATH) + await command('npm install', { + shell: true, + stdio: 'inherit' + }) + + process.chdir(currentWorkingDirectory) + const distBinPath = path.join(NODEJS_BRIDGE_DIST_PATH, 'bin') const distMainFilePath = path.join( - NODEJS_BRIDGE_DIST_PATH, - 'bin', - 'bridges', - 'nodejs', - 'src', - 'main.js' + distBinPath, + 'index.js' ) const distRenamedMainFilePath = path.join( - NODEJS_BRIDGE_DIST_PATH, - 'bin', + distBinPath, NODEJS_BRIDGE_BIN_NAME ) await fs.promises.rm(buildPath, { recursive: true, force: true }) - await command(`tsc --project ${tsconfigPath}`, { + const inputMainFilePath = path.join( + NODEJS_BRIDGE_ROOT_PATH, + 'src', + 'main.ts' + ) + + await command(`ncc build ${inputMainFilePath} --out ${distBinPath}`, { shell: true, stdio: 'inherit' }) From 426c816ca4555c473aa2ca4ce9cb6087ae697369 Mon Sep 17 00:00:00 2001 From: Divlo Date: Sun, 7 May 2023 01:14:44 +0200 Subject: [PATCH 17/19] build(bridge/nodejs): avoid installing dependencies each time we build --- scripts/build-binaries.js | 8 -------- 1 file changed, 8 deletions(-) diff --git a/scripts/build-binaries.js b/scripts/build-binaries.js index 99edeb78..dd3119ec 100644 --- a/scripts/build-binaries.js +++ b/scripts/build-binaries.js @@ -137,14 +137,6 @@ BUILD_TARGETS.set('tcp-server', { * Build for binaries not requiring a Python environment */ try { - const currentWorkingDirectory = process.cwd() - process.chdir(NODEJS_BRIDGE_ROOT_PATH) - await command('npm install', { - shell: true, - stdio: 'inherit' - }) - - process.chdir(currentWorkingDirectory) const distBinPath = path.join(NODEJS_BRIDGE_DIST_PATH, 'bin') const distMainFilePath = path.join( distBinPath, From 8147d8423b1ff6adcbeabf3ddda737aae3321443 Mon Sep 17 00:00:00 2001 From: louistiti Date: Sun, 7 May 2023 20:09:37 +0800 Subject: [PATCH 18/19] feat(scripts): add Node.js bridge on check script --- .../assets/nodejs-bridge-intent-object.json | 12 +++++ ....json => python-bridge-intent-object.json} | 0 scripts/check.js | 47 ++++++++++++++++++- 3 files changed, 57 insertions(+), 2 deletions(-) create mode 100644 scripts/assets/nodejs-bridge-intent-object.json rename scripts/assets/{intent-object.json => python-bridge-intent-object.json} (100%) diff --git a/scripts/assets/nodejs-bridge-intent-object.json b/scripts/assets/nodejs-bridge-intent-object.json new file mode 100644 index 00000000..1b7082eb --- /dev/null +++ b/scripts/assets/nodejs-bridge-intent-object.json @@ -0,0 +1,12 @@ +{ + "lang": "en", + "domain": "leon", + "skill": "age", + "action": "run", + "utterance": "How old are you?", + "slots": {}, + "entities": [], + "current_entities": [], + "resolvers": [], + "current_resolvers": [] +} diff --git a/scripts/assets/intent-object.json b/scripts/assets/python-bridge-intent-object.json similarity index 100% rename from scripts/assets/intent-object.json rename to scripts/assets/python-bridge-intent-object.json diff --git a/scripts/check.js b/scripts/check.js index aac95d91..b65876c7 100644 --- a/scripts/check.js +++ b/scripts/check.js @@ -16,9 +16,11 @@ import { SystemHelper } from '@/helpers/system-helper' import { MINIMUM_REQUIRED_RAM, LEON_VERSION, + NODEJS_BRIDGE_BIN_PATH, PYTHON_BRIDGE_BIN_PATH, TCP_SERVER_BIN_PATH, TCP_SERVER_VERSION, + NODEJS_BRIDGE_VERSION, PYTHON_BRIDGE_VERSION, INSTANCE_ID } from '@/constants' @@ -102,6 +104,13 @@ dotenv.config() skillsResolversModelState: null, mainModelState: null }, + nodeJSBridge: { + version: null, + executionTime: null, + command: null, + output: null, + error: null + }, pythonBridge: { version: 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}`) @@ -223,7 +266,7 @@ dotenv.config() process.cwd(), 'scripts', 'assets', - 'intent-object.json' + 'python-bridge-intent-object.json' )}"`, { shell: true } ) From 950985ca863d5cfeb1ac2c546cb74d8662bc1ba6 Mon Sep 17 00:00:00 2001 From: Divlo Date: Sun, 7 May 2023 15:07:20 +0200 Subject: [PATCH 19/19] feat(bridge/nodejs): finish network implementation --- bridges/nodejs/src/sdk/network.ts | 80 ++++++++++++++++++++++++++++-- skills/leon/age/src/actions/run.ts | 34 ++++++++++--- 2 files changed, 105 insertions(+), 9 deletions(-) diff --git a/bridges/nodejs/src/sdk/network.ts b/bridges/nodejs/src/sdk/network.ts index 572a33c2..f106b16f 100644 --- a/bridges/nodejs/src/sdk/network.ts +++ b/bridges/nodejs/src/sdk/network.ts @@ -1,10 +1,46 @@ import axios from 'axios' import type { AxiosInstance } from 'axios' -export interface NetworkOptions { +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 + + /** Custom headers to be sent. */ + headers?: Record +} + +interface NetworkResponse { + /** 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 extends Error { + public readonly response: NetworkResponse + + public constructor(response: NetworkResponse) { + super(`[NetworkError]: ${response.statusCode}`) + this.response = response + Object.setPrototypeOf(this, NetworkError.prototype) + } +} + export class Network { private options: NetworkOptions private axios: AxiosInstance @@ -16,7 +52,45 @@ export class Network { }) } - public async get(url: string): Promise { - return (await this.axios.get(url)).data as T + public async request( + options: NetworkRequestOptions + ): Promise> { + try { + const response = await this.axios.request({ + 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({ + data, + statusCode, + options: { + ...this.options, + ...options + } + }) + } + } + + public isNetworkError( + error: unknown + ): error is NetworkError { + return error instanceof NetworkError } } diff --git a/skills/leon/age/src/actions/run.ts b/skills/leon/age/src/actions/run.ts index d9312b44..fcbb3980 100644 --- a/skills/leon/age/src/actions/run.ts +++ b/skills/leon/age/src/actions/run.ts @@ -30,11 +30,33 @@ export const run: ActionFunction = async function () { const network = new Network({ baseURL: 'https://jsonplaceholder.typicode.com' }) - const data = await network.get<{ title: string }>('/todos/1') - await leon.answer({ - key: 'answer', - data: { - answer: `Todo n°1: ${data.title}` + + 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}` + } + }) } - }) + } }