1
1
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:
louistiti 2023-05-05 21:21:27 +08:00
parent 89a6c04a1e
commit b65cfb70d6
No known key found for this signature in database
GPG Key ID: 0A1C3B043E70C77D
10 changed files with 175 additions and 127 deletions

View 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

View File

@ -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)
} }
})() })()

View File

@ -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
}
}

View File

@ -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)
}
} }
} }

View File

@ -1,3 +0,0 @@
import type { AnswerObject } from '@bridge/types'
export type ActionResponse = AnswerObject | null

View File

@ -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
}
}

View File

@ -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')
)
}

View File

@ -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,

View File

@ -7,6 +7,7 @@
} }
}, },
"answers": { "answers": {
"default": ["I'm..."] "default": ["I'm..."],
"greet": ["Hey, just a try %name% again %name%", "Another try, hi"]
} }
} }

View File

@ -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'))
} }