mirror of
https://github.com/leon-ai/leon.git
synced 2024-12-25 17:54:43 +03:00
feat(bridge/nodejs): final answer bridge
This commit is contained in:
parent
89a6c04a1e
commit
b65cfb70d6
36
bridges/nodejs/src/constants.ts
Normal file
36
bridges/nodejs/src/constants.ts
Normal file
@ -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
|
@ -1,6 +1,6 @@
|
|||||||
import path from 'node:path'
|
import path from 'node:path'
|
||||||
|
|
||||||
import { getIntentObject } from '@bridge/utils'
|
import { INTENT_OBJECT } from '@bridge/constants'
|
||||||
;(async (): Promise<void> => {
|
;(async (): Promise<void> => {
|
||||||
const {
|
const {
|
||||||
domain,
|
domain,
|
||||||
@ -13,7 +13,7 @@ import { getIntentObject } from '@bridge/utils'
|
|||||||
current_resolvers: currentResolvers,
|
current_resolvers: currentResolvers,
|
||||||
resolvers,
|
resolvers,
|
||||||
slots
|
slots
|
||||||
} = await getIntentObject()
|
} = INTENT_OBJECT
|
||||||
|
|
||||||
const params = {
|
const params = {
|
||||||
lang,
|
lang,
|
||||||
@ -40,6 +40,6 @@ import { getIntentObject } from '@bridge/utils'
|
|||||||
|
|
||||||
await actionFunction(params)
|
await actionFunction(params)
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error while running action:', e)
|
console.error(`Error while running "${skill}" skill "${action}" action:`, e)
|
||||||
}
|
}
|
||||||
})()
|
})()
|
||||||
|
@ -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<AnswerObject['output']> {
|
|
||||||
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('<ul><li>Apples</li><li>Strawberries</li></ul>')
|
|
||||||
*/
|
|
||||||
public constructor(html: string) {
|
|
||||||
super()
|
|
||||||
this.html = html
|
|
||||||
}
|
|
||||||
}
|
|
@ -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'
|
interface IntentObject {
|
||||||
import type { Answer } from '@sdk/answer'
|
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<string, unknown>[] // TODO
|
||||||
|
}
|
||||||
|
interface AnswerOutput extends IntentObject {
|
||||||
|
output: {
|
||||||
|
code: string
|
||||||
|
speech: string
|
||||||
|
core: AnswerCoreData
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
options: Record<string, any>
|
||||||
|
}
|
||||||
|
}
|
||||||
|
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<string, string | number> | null
|
||||||
|
type AnswerInput = TextAnswer | WidgetAnswer
|
||||||
|
|
||||||
class Leon {
|
class Leon {
|
||||||
private static instance: Leon
|
private static instance: Leon
|
||||||
@ -13,34 +56,94 @@ class Leon {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Send an answer to the core
|
* Get source configuration
|
||||||
* @param answer
|
* @example getSRCConfig() // { credentials: { apiKey: 'abc' } }
|
||||||
*/
|
*/
|
||||||
public async answer(answer: Answer): Promise<void> {
|
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||||
|
public getSRCConfig(key?: string): Record<string, any> {
|
||||||
try {
|
try {
|
||||||
const answerObject: AnswerObject = {
|
if (key) {
|
||||||
...(await this.getIntentObject()),
|
return SKILL_SRC_CONFIG[key]
|
||||||
output: await answer.createAnswerOutput()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
process.stdout.write(JSON.stringify(answerObject))
|
return SKILL_SRC_CONFIG
|
||||||
} catch (error) {
|
} catch (e) {
|
||||||
console.error('Error while creating answer:', error)
|
console.error('Error while getting source configuration:', e)
|
||||||
|
|
||||||
|
return {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the intent object from the temporary intent file
|
* Apply data to the answer
|
||||||
* @example await getIntentObject() // { ... }
|
* @param answerKey The answer key
|
||||||
|
* @param data The data to apply
|
||||||
|
* @example setAnswerData('key', { name: 'Leon' })
|
||||||
*/
|
*/
|
||||||
private async getIntentObject(): Promise<IntentObject> {
|
public setAnswerData(
|
||||||
const {
|
answerKey: string,
|
||||||
argv: [, , INTENT_OBJ_FILE_PATH]
|
data: AnswerData = null
|
||||||
} = process
|
): string | null {
|
||||||
|
try {
|
||||||
|
// In case the answer key is a raw answer
|
||||||
|
if (!SKILL_CONFIG.answers[answerKey]) {
|
||||||
|
return answerKey
|
||||||
|
}
|
||||||
|
|
||||||
return JSON.parse(
|
const answers = SKILL_CONFIG.answers[answerKey]
|
||||||
await fs.promises.readFile(INTENT_OBJ_FILE_PATH as string, 'utf8')
|
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<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)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +0,0 @@
|
|||||||
import type { AnswerObject } from '@bridge/types'
|
|
||||||
|
|
||||||
export type ActionResponse = AnswerObject | 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<string, unknown>[] // TODO
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface AnswerObject extends IntentObject {
|
|
||||||
output: {
|
|
||||||
codes: string
|
|
||||||
speech: string
|
|
||||||
core: unknown // TODO
|
|
||||||
options: unknown // TODO
|
|
||||||
}
|
|
||||||
}
|
|
@ -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<IntentObject> {
|
|
||||||
return JSON.parse(
|
|
||||||
await fs.promises.readFile(INTENT_OBJ_FILE_PATH as string, 'utf8')
|
|
||||||
)
|
|
||||||
}
|
|
@ -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,
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
}
|
}
|
||||||
},
|
},
|
||||||
"answers": {
|
"answers": {
|
||||||
"default": ["I'm..."]
|
"default": ["I'm..."],
|
||||||
|
"greet": ["Hey, just a try %name% again %name%", "Another try, hi"]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,11 +1,15 @@
|
|||||||
import { leon } from '@sdk/leon'
|
import { leon } from '@sdk/leon'
|
||||||
import { TextAnswer } from '@sdk/answer'
|
|
||||||
import { Button } from '@sdk/aurora/button'
|
import { Button } from '@sdk/aurora/button'
|
||||||
|
|
||||||
export async function run(): Promise<void> {
|
export async function run(): Promise<void> {
|
||||||
await leon.answer(new TextAnswer('intermediate answer'))
|
await leon.answer({ key: 'default' })
|
||||||
|
|
||||||
|
await leon.answer({
|
||||||
|
key: 'greet',
|
||||||
|
data: {
|
||||||
|
name: 'Louis'
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
console.log('button', Button)
|
console.log('button', Button)
|
||||||
|
|
||||||
await leon.answer(new TextAnswer('final answer'))
|
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user