mirror of
https://github.com/leon-ai/leon.git
synced 2024-12-24 17:23:23 +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 { getIntentObject } from '@bridge/utils'
|
||||
import { INTENT_OBJECT } from '@bridge/constants'
|
||||
;(async (): Promise<void> => {
|
||||
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)
|
||||
}
|
||||
})()
|
||||
|
@ -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'
|
||||
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<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 {
|
||||
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<void> {
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
public getSRCConfig(key?: string): Record<string, any> {
|
||||
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<IntentObject> {
|
||||
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<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
|
||||
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,
|
||||
|
@ -7,6 +7,7 @@
|
||||
}
|
||||
},
|
||||
"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 { TextAnswer } from '@sdk/answer'
|
||||
import { Button } from '@sdk/aurora/button'
|
||||
|
||||
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)
|
||||
|
||||
await leon.answer(new TextAnswer('final answer'))
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user