1
1
mirror of https://github.com/leon-ai/leon.git synced 2024-11-30 10:44:25 +03:00

Merge branch 'feat/widgets' into develop

This commit is contained in:
louistiti 2024-08-25 21:55:51 +08:00
commit 534853d67a
No known key found for this signature in database
GPG Key ID: 92CD6A2E497E1669
12 changed files with 166 additions and 110 deletions

View File

@ -1,7 +1,7 @@
import { createElement } from 'react'
import { createRoot } from 'react-dom/client'
import axios from 'axios'
import { WidgetWrapper, Flexbox, Loader } from '@leon-ai/aurora'
import { WidgetWrapper, Flexbox, Loader, Text } from '@leon-ai/aurora'
import renderAuroraComponent from './render-aurora-component'
@ -130,19 +130,33 @@ export default class Chatbot {
const data = await axios.get(
`${this.serverURL}/api/v1/fetch-widget?skill_action=${widgetContainer.onFetchAction}&widget_id=${widgetContainer.widgetId}`
)
console.log('data', data.data)
const fetchedWidget = data.data.widget
const reactNode = renderAuroraComponent(
this.socket,
fetchedWidget.componentTree,
fetchedWidget.supportedEvents
)
const reactNode = fetchedWidget
? renderAuroraComponent(
this.socket,
fetchedWidget.componentTree,
fetchedWidget.supportedEvents
)
: createElement(WidgetWrapper, {
children: createElement(Flexbox, {
alignItems: 'center',
justifyContent: 'center',
children: createElement(Text, {
secondary: true,
children: 'This widget has been deleted.'
})
})
})
widgetContainer.reactRootNode.render(reactNode)
WIDGETS_FETCH_CACHE.set(widgetContainer.widgetId, {
...fetchedWidget,
reactNode
})
this.scrollDown()
setTimeout(() => {
this.scrollDown()
}, 100)
}
}
})

View File

@ -44,18 +44,26 @@ class Leon {
if (data != null) {
for (const key in data) {
// In case the answer needs speech and text differentiation
if (typeof answer !== 'string' && answer.text) {
answer.text = answer.text.replaceAll(`%${key}%`, String(data[key]))
answer.speech = answer.speech.replaceAll(
`%${key}%`,
String(data[key])
)
} else {
if (typeof answer === 'string') {
answer = (answer as string).replaceAll(
`%${key}%`,
String(data[key])
)
} else {
// In case the answer needs speech and text differentiation
if (answer.text) {
answer.text = answer.text.replaceAll(
`%${key}%`,
String(data[key])
)
}
if (answer.speech) {
answer.speech = answer.speech.replaceAll(
`%${key}%`,
String(data[key])
)
}
}
}
}
@ -64,21 +72,26 @@ class Leon {
const { variables } = SKILL_CONFIG
for (const key in variables) {
// In case the answer needs speech and text differentiation
if (typeof answer !== 'string' && answer.text) {
answer.text = answer.text.replaceAll(
`%${key}%`,
String(variables[key])
)
answer.speech = answer.speech.replaceAll(
`%${key}%`,
String(variables[key])
)
} else {
if (typeof answer === 'string') {
answer = (answer as string).replaceAll(
`%${key}%`,
String(variables[key])
)
} else {
// In case the answer needs speech and text differentiation
if (answer.text) {
answer.text = answer.text.replaceAll(
`%${key}%`,
String(variables[key])
)
}
if (answer.speech) {
answer.speech = answer.speech.replaceAll(
`%${key}%`,
String(variables[key])
)
}
}
}
}

View File

@ -1,10 +1,5 @@
{
"endpoints": [
{
"method": "GET",
"route": "/api/action/unknown/widget-playground/run",
"params": []
},
{
"method": "POST",
"route": "/api/action/games/akinator/choose_thematic",
@ -57,17 +52,6 @@
"route": "/api/action/games/rochambeau/rematch",
"params": []
},
{
"method": "POST",
"route": "/api/action/news/github_trends/run",
"params": ["number", "daterange"],
"entitiesType": "builtIn"
},
{
"method": "GET",
"route": "/api/action/news/product_hunt_trends/run",
"params": []
},
{
"method": "GET",
"route": "/api/action/leon/age/run",
@ -143,6 +127,17 @@
"route": "/api/action/leon/thanks/run",
"params": []
},
{
"method": "POST",
"route": "/api/action/news/github_trends/run",
"params": ["number", "daterange"],
"entitiesType": "builtIn"
},
{
"method": "GET",
"route": "/api/action/news/product_hunt_trends/run",
"params": []
},
{
"method": "POST",
"route": "/api/action/productivity/todo_list/create_list",
@ -190,6 +185,36 @@
"params": ["todos", "list"],
"entitiesType": "trim"
},
{
"method": "GET",
"route": "/api/action/social_communication/conversation/setup",
"params": []
},
{
"method": "GET",
"route": "/api/action/social_communication/conversation/chit_chat",
"params": []
},
{
"method": "GET",
"route": "/api/action/social_communication/conversation/converse",
"params": []
},
{
"method": "GET",
"route": "/api/action/social_communication/mbti/setup",
"params": []
},
{
"method": "GET",
"route": "/api/action/social_communication/mbti/quiz",
"params": []
},
{
"method": "GET",
"route": "/api/action/unknown/widget-playground/run",
"params": []
},
{
"method": "GET",
"route": "/api/action/utilities/date_time/current_date_time",
@ -246,16 +271,6 @@
"route": "/api/action/utilities/timer/cancel_timer",
"params": []
},
{
"method": "GET",
"route": "/api/action/utilities/timer/pause_timer",
"params": []
},
{
"method": "GET",
"route": "/api/action/utilities/timer/resume_timer",
"params": []
},
{
"method": "GET",
"route": "/api/action/utilities/timer/check_timer",
@ -275,31 +290,6 @@
"method": "GET",
"route": "/api/action/utilities/translator-poc/translate",
"params": []
},
{
"method": "GET",
"route": "/api/action/social_communication/conversation/setup",
"params": []
},
{
"method": "GET",
"route": "/api/action/social_communication/conversation/chit_chat",
"params": []
},
{
"method": "GET",
"route": "/api/action/social_communication/conversation/converse",
"params": []
},
{
"method": "GET",
"route": "/api/action/social_communication/mbti/setup",
"params": []
},
{
"method": "GET",
"route": "/api/action/social_communication/mbti/quiz",
"params": []
}
]
}

View File

@ -182,7 +182,7 @@ export default class Brain {
}, naturalStartTypingDelay)
// Next answer to handle
const answer = this.answerQueue.pop()
let textAnswer = ''
let textAnswer: string | undefined = ''
let speechAnswer = ''
if (answer && answer !== '') {
@ -208,21 +208,33 @@ export default class Brain {
!hasLoopConfig &&
!hasSlotsConfig
) {
if (speechAnswer === textAnswer || typeof answer === 'string') {
if (
speechAnswer === textAnswer ||
typeof answer === 'string' ||
answer.speech
) {
/**
* Only use LLM NLG if the answer is not too short
* otherwise it will be too hard for the model to generate a meaningful text
*/
const nbOfWords = String(answer).split(' ').length
const textToParaphrase = textAnswer ?? speechAnswer
const nbOfWords = String(textToParaphrase).split(' ').length
if (nbOfWords >= MIN_NB_OF_WORDS_TO_USE_LLM_NLG) {
const paraphraseDuty = new ParaphraseLLMDuty({
input: textAnswer
input: textToParaphrase
})
await paraphraseDuty.init()
const paraphraseResult = await paraphraseDuty.execute()
const paraphraseResult = await paraphraseDuty.execute({
// Do not generate tokens when only a speech answer is needed
shouldEmitOnToken: !!(!textAnswer && speechAnswer)
})
textAnswer = paraphraseResult?.output as unknown as string
speechAnswer = textAnswer
if (!textAnswer) {
speechAnswer = paraphraseResult?.output as unknown as string
} else {
textAnswer = paraphraseResult?.output as unknown as string
speechAnswer = textAnswer
}
}
}
}
@ -249,17 +261,24 @@ export default class Brain {
})
})*/
SOCKET_SERVER.socket?.emit('answer', textAnswer)
SOCKET_SERVER.socket?.emit('is-typing', false)
/**
* Only send an answer when the text answer is defined.
* It may happen that only a speech is needed
*/
if (textAnswer) {
SOCKET_SERVER.socket?.emit('answer', textAnswer)
await CONVERSATION_LOGGER.push({
who: 'owner',
message: NLU.nluResult.newUtterance
})
await CONVERSATION_LOGGER.push({
who: 'leon',
message: textAnswer
})
await CONVERSATION_LOGGER.push({
who: 'owner',
message: NLU.nluResult.newUtterance
})
await CONVERSATION_LOGGER.push({
who: 'leon',
message: textAnswer
})
}
SOCKET_SERVER.socket?.emit('is-typing', false)
}
}
@ -448,7 +467,7 @@ export default class Brain {
const { answer } = skillAnswer.output
if (!this.isMuted) {
this.talk(answer)
this.talk(answer, true)
}
this.skillOutput = data.toString()

View File

@ -138,7 +138,7 @@ The sun is a star, it is the closest star to Earth.`
session: ParaphraseLLMDuty.session,
maxTokens: LLM_MANAGER.context.contextSize,
onToken: (chunk) => {
if (!params.isWarmingUp) {
if (!params.isWarmingUp && !params.shouldEmitOnToken) {
const detokenizedChunk = LLM_PROVIDER.cleanUpResult(
LLM_MANAGER.model.detokenize(chunk)
)

View File

@ -27,6 +27,7 @@ export interface LLMDutyInitParams {
}
export interface LLMDutyExecuteParams {
isWarmingUp?: boolean
shouldEmitOnToken?: boolean
}
export interface LLMDutyParams {
input: string | null
@ -46,7 +47,8 @@ export const DEFAULT_INIT_PARAMS: LLMDutyInitParams = {
force: false
}
export const DEFAULT_EXECUTE_PARAMS: LLMDutyExecuteParams = {
isWarmingUp: false
isWarmingUp: false,
shouldEmitOnToken: true
}
export abstract class LLMDuty {

View File

@ -28,7 +28,7 @@ const answerTypes = Type.Union([
Type.String(),
Type.Object({
speech: Type.String(),
text: Type.String()
text: Type.Optional(Type.String())
})
])
const skillCustomEnumEntityType = Type.Object(

View File

@ -19,14 +19,6 @@
"Don't need the timer"
]
},
"pause_timer": {
"type": "logic",
"utterance_samples": ["Pause the timer", "Put the timer on hold"]
},
"resume_timer": {
"type": "logic",
"utterance_samples": ["[Resume|Continue] the timer"]
},
"check_timer": {
"type": "logic",
"utterance_samples": [
@ -37,6 +29,11 @@
}
},
"answers": {
"timer_set": [
{
"speech": "Done. I will let you know when time is up."
}
],
"cannot_get_duration": [
"You should provide a duration for the timer.",
"You didn't provide a duration for the timer."
@ -45,7 +42,8 @@
"Sorry, I can't set a timer for this unit. Use %hours%, %minutes% or %seconds% instead.",
"I can't set a timer for this duration. Use %hours%, %minutes% or %seconds% instead."
],
"no_timer_set": ["No timer is set.", "There is no timer set."]
"no_timer_set": ["No timer is set.", "There is no timer set."],
"timer_canceled": ["The timer is canceled.", "Timer is stopped."]
},
"widget_contents": {
"second_unit": "second",

View File

@ -0,0 +1,10 @@
import type { ActionFunction } from '@sdk/types'
import { leon } from '@sdk/leon'
import { deleteAllTimersMemory } from '../lib/memory'
export const run: ActionFunction = async function () {
await deleteAllTimersMemory()
await leon.answer({ key: 'timer_canceled' })
}

View File

@ -16,13 +16,16 @@ export const run: ActionFunction = async function () {
}
const { interval, finishedAt, duration } = timerMemory
const remainingTime = finishedAt - Math.floor(Date.now() / 1_000)
let remainingTime = finishedAt - Math.floor(Date.now() / 1_000)
if (remainingTime <= 0) {
remainingTime = 0
}
const initialProgress = 100 - (remainingTime / duration) * 100
const timerWidget = new TimerWidget({
params: {
id: widgetId ?? timerMemory.widgetId,
seconds: remainingTime <= 0 ? 0 : remainingTime,
seconds: remainingTime,
initialProgress,
initialDuration: duration,
interval

View File

@ -39,5 +39,8 @@ export const run: ActionFunction = async function (params) {
widget: timerWidget,
speech: 'I set a timer for ... ...'
})*/
await leon.answer({ widget: timerWidget })
await leon.answer({
widget: timerWidget,
key: 'timer_set'
})
}

View File

@ -49,3 +49,7 @@ export async function getNewestTimerMemory(): Promise<TimerMemory | null> {
return timersMemory[timersMemory.length - 1] || null
}
export function deleteAllTimersMemory(): Promise<TimerMemory[]> {
return TIMERS_MEMORY.write([])
}