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.

+
@@ -21,7 +22,7 @@
- +
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": {