From 4b5a883510fd4421a491f999cc21d8f7dd369a03 Mon Sep 17 00:00:00 2001
From: louistiti
Date: Sun, 19 Jun 2022 13:47:43 +0800
Subject: [PATCH] feat: communicate suggestions to the client
---
app/src/css/style.css | 64 +++++++++++++++++------
app/src/index.html | 3 +-
app/src/js/client.js | 10 +++-
app/src/js/main.js | 5 +-
server/src/core/brain.js | 7 ++-
server/src/core/nlu.js | 9 +++-
skills/games/guess_the_number/nlu/en.json | 4 ++
skills/games/rochambeau/nlu/en.json | 9 ++++
8 files changed, 88 insertions(+), 23 deletions(-)
diff --git a/app/src/css/style.css b/app/src/css/style.css
index 8047240d..d6b86375 100644
--- a/app/src/css/style.css
+++ b/app/src/css/style.css
@@ -46,6 +46,11 @@ table {
outline: none;
}
+:root {
+ --black-color: #151718;
+ --white-color: #FFF;
+}
+
a {
color: inherit;
}
@@ -55,8 +60,8 @@ ul li {
}
body {
- color: #FFF;
- background-color: #151718;
+ color: var(--white-color);
+ background-color: var(--black-color);
font-family: 'Open Sans', sans-serif;
font-weight: 400;
}
@@ -107,10 +112,10 @@ footer {
input {
text-align: center;
- color: #FFF;
+ color: var(--white-color);
width: 100%;
border: none;
- border-bottom: 2px solid #FFF;
+ border-bottom: 2px solid var(--white-color);
background: none;
font-weight: 400;
font-size: 4em;
@@ -118,7 +123,7 @@ input {
}
small {
- color: #FFF;
+ color: var(--white-color);
font-size: .7em;
}
@@ -139,7 +144,7 @@ small {
top: 10%;
height: 50%;
overflow-y: auto;
- border: 2px solid #FFF;
+ border: 2px solid var(--white-color);
border-radius: 12px;
}
#feed::-webkit-scrollbar {
@@ -172,7 +177,7 @@ small {
border-radius: 50%;
width: 10px;
height: 10px;
- background-color: #FFF;
+ background-color: var(--white-color);
transform: scale(1);
}
#is-typing .circle:nth-child(1) {
@@ -181,7 +186,7 @@ small {
}
#is-typing .circle:nth-child(2) {
animation: typing .2s .2s linear infinite alternate;
- background-color: #FFF;
+ background-color: var(--white-color);
}
#is-typing .circle:nth-child(3) {
animation: typing .2s linear infinite alternate;
@@ -215,12 +220,12 @@ small {
}
#feed .me .bubble {
background-color: #1C75DB;
- color: #FFF;
+ color: var(--white-color);
right: 0;
}
#feed .leon .bubble {
background-color: #EEE;
- color: #151718;
+ color: var(--black-color);
}
@keyframes fadeIn {
100% {
@@ -228,10 +233,35 @@ small {
}
}
+#suggestions-container {
+ position: absolute;
+ z-index: 10;
+ width: 100%;
+ bottom: 36%;
+ display: flex;
+ justify-content: flex-end;
+ column-gap: 8px;
+ overflow-x: auto;
+}
+.suggestion {
+ border: 1px solid var(--white-color);
+ background-color: transparent;
+ color: var(--white-color);
+ border-radius: 8px;
+ padding: 2px 8px;
+ font-size: inherit;
+ cursor: pointer;
+ transition: background-color .2s, color .2s;
+}
+.suggestion:hover {
+ color: var(--black-color);
+ background-color: var(--white-color);
+}
+
#input-container {
position: absolute;
width: 100%;
- bottom: 25%;
+ bottom: 22%;
}
#mic-container {
@@ -244,7 +274,7 @@ small {
font-style: italic;
}
-button {
+#mic-button {
position: absolute;
border: none;
cursor: pointer;
@@ -256,16 +286,16 @@ button {
mask-image: url(../img/mic.svg);
transition: background-color .2s;
}
-button:not(.enabled) {
+#mic-button:not(.enabled) {
margin-left: -26px;
}
-button:hover {
- background-color: #FFF;
+#mic-button:hover {
+ background-color: var(--white-color);
}
-button.enabled {
+#mic-button.enabled {
background-color: #00E676;
}
-button.enabled + #sonar {
+#mic-button.enabled + #sonar {
width: 26px;
height: 26px;
border-radius: 50%;
diff --git a/app/src/index.html b/app/src/index.html
index 1d4de704..86981e6a 100644
--- a/app/src/index.html
+++ b/app/src/index.html
@@ -14,6 +14,7 @@
You can start to interact with Leon, don't be shy.
+
diff --git a/app/src/js/client.js b/app/src/js/client.js
index 3c2840cc..8270a334 100644
--- a/app/src/js/client.js
+++ b/app/src/js/client.js
@@ -2,9 +2,10 @@ import { io } from 'socket.io-client'
import Chatbot from './chatbot'
export default class Client {
- constructor (client, serverUrl, input, res) {
+ constructor (client, serverUrl, input, suggestionsContainer, res) {
this.client = client
this._input = input
+ this._suggestionContainer = suggestionsContainer
this.serverUrl = serverUrl
this.socket = io(this.serverUrl)
this.history = localStorage.getItem('history')
@@ -43,6 +44,13 @@ export default class Client {
this.chatbot.receivedFrom('leon', data)
})
+ this.socket.on('suggest', (data) => {
+ data.forEach((suggestion) => {
+ this._suggestionContainer.innerHTML
+ += `
`
+ })
+ })
+
this.socket.on('is-typing', (data) => {
this.chatbot.isTyping('leon', data)
})
diff --git a/app/src/js/main.js b/app/src/js/main.js
index f144f8b5..c3c2a915 100644
--- a/app/src/js/main.js
+++ b/app/src/js/main.js
@@ -26,10 +26,11 @@ document.addEventListener('DOMContentLoaded', () => {
console.error(err.response.error.message)
} else {
const input = document.querySelector('#utterance')
- const mic = document.querySelector('button')
+ const mic = document.querySelector('#mic-button')
const v = document.querySelector('#version small')
const logger = document.querySelector('#logger small')
- const client = new Client(config.app, serverUrl, input, res.body)
+ const suggestionsContainer = document.querySelector('#suggestions-container')
+ const client = new Client(config.app, serverUrl, input, suggestionsContainer, res.body)
let rec = { }
let chunks = []
let sLogger = ' enabled, thank you.'
diff --git a/server/src/core/brain.js b/server/src/core/brain.js
index 2a642f36..8b4206de 100644
--- a/server/src/core/brain.js
+++ b/server/src/core/brain.js
@@ -161,9 +161,14 @@ class Brain {
const { nluDataFilePath, classification: { action: actionName } } = obj
const { actions } = JSON.parse(fs.readFileSync(nluDataFilePath, 'utf8'))
const action = actions[actionName]
- const { type: actionType } = action
+ const { type: actionType, suggestions } = action
const nextAction = action.next_action ? actions[action.next_action] : null
+ // Send suggestions to the client if this action does not contain an action loop
+ if (suggestions && !action.loop) {
+ this._socket.emit('suggest', suggestions)
+ }
+
if (actionType === 'logic') {
/**
* "Logic" action skill execution
diff --git a/server/src/core/nlu.js b/server/src/core/nlu.js
index 029e8dd6..39d30c43 100644
--- a/server/src/core/nlu.js
+++ b/server/src/core/nlu.js
@@ -188,7 +188,9 @@ class Nlu {
const { actions } = JSON.parse(fs.readFileSync(nluDataFilePath, 'utf8'))
const action = actions[this.nluResultObj.classification.action]
- const { name: expectedItemName, type: expectedItemType } = action.loop.expected_item
+ const {
+ name: expectedItemName, type: expectedItemType
+ } = action.loop.expected_item
let hasMatchingEntity = false
let hasMatchingResolver = false
@@ -244,6 +246,11 @@ class Nlu {
// Break the action loop and prepare for the next action if necessary
if (processedData.core?.isInActionLoop === false) {
+ // Send suggestions to the client only at the end of the action loop
+ if (action.suggestions) {
+ this.brain.socket.emit('suggest', action.suggestions)
+ }
+
this.conv.activeContext.isInActionLoop = !!processedData.action.loop
this.conv.activeContext.actionName = processedData.action.next_action
this.conv.activeContext.intent = `${processedData.classification.skill}.${processedData.action.next_action}`
diff --git a/skills/games/guess_the_number/nlu/en.json b/skills/games/guess_the_number/nlu/en.json
index 55bde77d..ec7e02bb 100644
--- a/skills/games/guess_the_number/nlu/en.json
+++ b/skills/games/guess_the_number/nlu/en.json
@@ -18,6 +18,10 @@
"name": "number"
}
},
+ "suggestions": [
+ "Yes",
+ "No thanks"
+ ],
"next_action": "replay"
},
"replay": {
diff --git a/skills/games/rochambeau/nlu/en.json b/skills/games/rochambeau/nlu/en.json
index d320883b..2c2a2fbf 100644
--- a/skills/games/rochambeau/nlu/en.json
+++ b/skills/games/rochambeau/nlu/en.json
@@ -13,6 +13,11 @@
"1, 2, 3, FIRE!",
"Rock, paper, scissors..."
],
+ "suggestions": [
+ "Rock ✊",
+ "Paper ✋",
+ "Scissors ✌"
+ ],
"next_action": "play"
},
"play": {
@@ -23,6 +28,10 @@
"name": "handsign"
}
},
+ "suggestions": [
+ "Yes",
+ "No thanks"
+ ],
"next_action": "rematch"
},
"rematch": {