1
1
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:
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 { 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)
}
})()

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

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

View File

@ -7,6 +7,7 @@
}
},
"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 { 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'))
}