Initial commit

This commit is contained in:
Andreas Greiner 2021-06-13 13:51:44 +02:00
commit 15a6bcb07f
13 changed files with 2236 additions and 0 deletions

1
.gitignore vendored Normal file
View File

@ -0,0 +1 @@
node_modules/

17
.vscode/launch.json vendored Normal file
View File

@ -0,0 +1,17 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Launch Program",
"skipFiles": [
"<node_internals>/**"
],
"program": "${file}"
}
]
}

1
README.md Normal file
View File

@ -0,0 +1 @@
Chatbot providing information about Raspberry Pi 4

173
bot.js Normal file
View File

@ -0,0 +1,173 @@
'use strict'
/* Pakete die wir brauchen */
const querystring = require('querystring');
const fetch = require('node-fetch');
var WebSocketClient = require('websocket').client;
/** Wissensbasis importieren und als Dictionary abspeichern */
var wissensbasis = require('./wissensbasis.json');
var answerDict = {};
wissensbasis.intents.forEach(e => {
answerDict[e.intent] = e.answers;
});
var repeat1Dict = {};
wissensbasis.intents.forEach(e => {
repeat1Dict[e.intent] = e.repeat1;
});
var repeat2Dict = {};
wissensbasis.intents.forEach(e => {
repeat2Dict[e.intent] = e.repeat2;
});
var fallbacks = [];
wissensbasis.fallbacks.forEach(e => {
fallbacks.push(e);
});
/* Die Intents der letzten 2 Anfragen werden hier gespeichert */
var savedIntents = {};
/** bot ist ein einfacher Websocket Chat Client */
class bot {
/**
* Konstruktor baut den client auf. Er erstellt einen Websocket und verbindet sich zum Server
* Bitte beachten Sie, dass die Server IP hardcodiert ist. Sie müssen sie umsetzten
*/
constructor () {
/** Die Websocketverbindung */
this.client = new WebSocketClient()
/** Der Name des Bots */
this.botname = "Raspi-Bot"
/** Wenn der Websocket verbunden ist, dann setzten wir ihn auf true */
this.connected = false
/** LUIS Endpoint-Parameter */
this.endpointKey = "9c4cdfefdcde4156a0735c0d2100ef3e";
this.endpoint = "raspberry-pi-4-chatbot.cognitiveservices.azure.com/";
this.appId = "87bb26ac-91ee-4006-a144-6cc7db61b0fb";
/** Wenn die Verbindung nicht zustande kommt, dann läuft der Aufruf hier hinein */
this.client.on('connectFailed', function (error) {
console.log('[BOT] Connect Error: ' + error.toString())
})
/** Wenn der Client sich mit dem Server verbindet sind wir hier */
this.client.on('connect', function (connection) {
this.con = connection
console.log('[BOT] WebSocket Client Connected')
connection.on('error', function (error) {
console.log('[BOT] Connection Error: ' + error.toString())
})
/**
* Es kann immer sein, dass sich der Client disconnected
* (typischer Weise, wenn der Server nicht mehr da ist)
*/
connection.on('close', function () {
console.log('[BOT] echo-protocol Connection Closed')
})
/**
* Hier ist der Kern, wenn immmer eine Nachricht empfangen wird, kommt hier die
* Nachricht an.
*/
connection.on('message', function (message) {
if (message.type === 'utf8') {
var data = JSON.parse(message.utf8Data)
console.log('[BOT] Received: ' + data.msg + ' ' + data.name)
}
})
/**
* Hier senden wir unsere Kennung damit der Server uns erkennt.
* Wir formatieren die Kennung als JSON
*/
function joinGesp () {
if (connection.connected) {
connection.sendUTF('{"type": "join", "name":"Raspi-Bot"}')
}
}
joinGesp()
})
}
/**
* Methode um sich mit dem Server zu verbinden.
*/
connect () {
this.client.connect('ws://localhost:8181/', 'chat')
this.connected = true
}
/**
* Hier muss ihre Verarbeitungslogik integriert werden.
* Diese Funktion wird automatisch im Server aufgerufen, wenn etwas ankommt, das wir
* nicht geschrieben haben
* @param nachricht auf die der bot reagieren soll
* @param nachricht name des Absenders
*/
async post (nachricht, uname) {
// Initialisiert den Intent-Speicher falls dies noch nicht geschehen ist
if(savedIntents[uname] == undefined){
savedIntents[uname] = [];
}
// Sendet die Nachricht an LUIS und erhält extrahiert den Top-Intent und dessen Score aus der Antwort
var luisResponse = await this.sendLUISrequest(nachricht);
var intent = luisResponse.prediction.topIntent;
var intentScore = luisResponse.prediction.intents[intent].score;
// Antwort anhand des erhaltenen Intents und den vorherigen Intents ermitteln
var answer;
if(intentScore > 0.5){
if(intent != savedIntents[uname][0]){
var answerArray = answerDict[intent]
answer = answerArray[Math.floor(Math.random() * answerArray.length)];
}else{
if(intent == savedIntents[uname][1]){
var repeat2Array = repeat2Dict[intent]
answer = repeat2Array[Math.floor(Math.random() * repeat2Array.length)];
}else{
var repeat1Array = repeat1Dict[intent]
answer = repeat1Array[Math.floor(Math.random() * repeat1Array.length)];
}
}
// Speichert den neuen Intent ab
savedIntents[uname][1] = savedIntents[uname][0];
savedIntents[uname][0] = intent;
}else{
answer = fallbacks[Math.floor(Math.random() * fallbacks.length)];
}
// Antwort zusammensetzen und absenden
var msg = '{"type": "msg", "name": "Raspi-Bot", "msg":"' + answer + '"}'
this.client.con.sendUTF(msg)
}
/**
* Sendet die Nachricht zum LUIS Endpoint
*
* @param message die Nachricht
* @returns die Antwort
*/
async sendLUISrequest(message){
var queryParams = {
"show-all-intents": true,
"verbose": true,
"query": message,
"subscription-key": this.endpointKey
}
var URI =`https://${this.endpoint}/luis/prediction/v3.0/apps/${this.appId}/slots/production/predict?${querystring.stringify(queryParams)}`;
var result
await fetch(URI).then(d=>d.json()).then(e=>{
result = e
});
return result;
}
}
module.exports = bot

1129
package-lock.json generated Normal file

File diff suppressed because it is too large Load Diff

20
package.json Normal file
View File

@ -0,0 +1,20 @@
{
"name": "raspibot",
"version": "1.0.0",
"description": "ChatBot to get information about the Raspberry Pi 4",
"main": "staticExpress.js",
"scripts": {
"start": "node staticExpress.js"
},
"keywords": [
"bot"
],
"author": "Andreas Greiner",
"license": "ISC",
"dependencies": {
"express": "^4.17.1",
"node-fetch": "^2.6.1",
"querystring": "^0.2.1",
"websocket": "^1.0.34"
}
}

153
public/css/newchat.css Normal file

File diff suppressed because one or more lines are too long

30
public/index.html Normal file
View File

@ -0,0 +1,30 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="IE=edge">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link href="./css/newchat.css" rel="stylesheet" type="text/css">
<script src="./js/newchat.js"></script>
<title>Raspi-Bot</title>
</head>
<body>
<section class="msger">
<header class="msger-header">
<div class="msger-header-title">
<i class="fas fa-comment-alt">Raspberry Pi 4 Chat Bot</i>
</div>
<div class="msger-header-options">
<span><i class="fas fa-cog"></i></span>
</div>
</header>
<main class="msger-chat"></main>
<form class="msger-inputarea" id="test">
<input type="text" class="msger-input" placeholder="Enter your message...">
<button type="submit" class="msger-send-btn">Send</button>
</form>
</section>
</body>
</html>

107
public/js/newchat.js Normal file
View File

@ -0,0 +1,107 @@
var msgerChat = null;
var socketurl = 'ws://127.0.0.1:8181/';
/** Standartnachricht ausgeben und Websocket-Verbindung aufbauen */
window.onload=function(){
/* DOM-Variablen initialisieren */
var msgerForm = get(".msger-inputarea");
var msgerInput = get(".msger-input");
msgerChat = get(".msger-chat");
/* Standard Wilkommensnachricht ausgeben */
appendMessage(BOT_NAME, BOT_IMG, "left", "Hi, wilkommen beim Raspi-Bot!");
/* Websocket initialisieren */
var socket = new WebSocket(socketurl, 'chat');
var username = 'u1'
socket.onopen = function () {
username = "name" + Math.floor(Math.random() * Math.floor(700));
socket.send('{"type": "join", "name":" '+username+'"}');
}
/* Aktion bei Nachrichteneingang festlegen */
socket.onmessage = function (msg) {
var data = JSON.parse(msg.data);
if(data.type == 'msg'){
if(data.name !== "Raspi-Bot" ){
appendMessage("Nutzer", PERSON_IMG, "right", data.msg);
}else{
appendMessage(BOT_NAME, BOT_IMG, "left", data.msg);
}
}
};
/* Aktion zum Absenden einer Nachricht festlegen */
msgerForm.addEventListener("submit", event => {
event.preventDefault();
const msgText = msgerInput.value;
if (!msgText) return;
socket.send('{"type": "msg", "msg": "' + msgerInput.value + '"}');
msgerInput.value = "";
});
}
/* Icons und Botname für die Chat-Bubbles */
const BOT_IMG = "https://image.flaticon.com/icons/svg/327/327779.svg";
const PERSON_IMG = "https://image.flaticon.com/icons/svg/145/145867.svg";
const BOT_NAME = "Raspi-Bot";
/**
* Neue Nachricht ins HTML hinzufügen
*
* @param img for the backround of the bubble
* @param side eighter left or right
* @param text the message text
*/
function appendMessage(name, img, side, text) {
const msgHTML = `
<div class="msg ${side}-msg">
<div class="msg-img" style="background-image: url(${img})"></div>
<div class="msg-bubble">
<div class="msg-info">
<div class="msg-info-name">${name}</div>
<div class="msg-info-time">${formatDate(new Date())}</div>
</div>
<div class="msg-text">${text}</div>
</div>
</div>
`;
msgerChat.insertAdjacentHTML("beforeend", msgHTML);
msgerChat.scrollTop += 500;
}
/**
* DOM-Elemente selektieren
*
* @param selector der Selektor (z.B. Klasse oder ID)
* @param root das Root-Element von dem ausgegangen wird
* @returns das DOM-Element
*/
function get(selector, root = document) {
return root.querySelector(selector);
}
/**
* Formatiert einen Datumswert zum Uhrzeit-Format HH:mm
*
* @param {*} date das Datum
* @returns die formatierte Uhrzeit
*/
function formatDate(date) {
const h = "0" + date.getHours();
const m = "0" + date.getMinutes();
return `${h.slice(-2)}:${m.slice(-2)}`;
}
/**
* Gibt eine zufällige ganze Zahl zwischen min und max zurück
*
* @param {*} min die Untergrenze
* @param {*} max die Obergrenze
* @returns die Zufallszahl
*/
function random(min, max) {
return Math.floor(Math.random() * (max - min) + min);
}

179
public/pics/pi4.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 40 KiB

312
public/pics/pi4_gray.svg Normal file

File diff suppressed because one or more lines are too long

After

Width:  |  Height:  |  Size: 92 KiB

96
staticExpress.js Normal file
View File

@ -0,0 +1,96 @@
/* Pakete die wir brauchen */
var bot = require('./bot.js')
var express = require('express')
const fs = require('fs')
// Erzeugt die Express Applikation
var app = express()
/* Nutzen einer statischen WebSeite */
app.use(express.static('public'))
// Wir nutzen ein paar statische Ressourcen
app.use('/css', express.static(__dirname + '/public/css'))
app.use('/js', express.static(__dirname + '/public/js'))
app.use('/pics', express.static(__dirname + '/public/pics'))
// Wir starten den Express server
var server = app.listen(8081, function () {
var address = server.address()
console.log(address)
console.log('Server started at http://localhost:8081')
})
// Das brauchen wir für unsere Websockets
var WSS = require('websocket').server,
http = require('http')
// Erzeugt den Webserver und hört auf den angegebenen Port
var server = http.createServer()
server.listen(8181)
/* Wir erstellen einen Bot, der kann sich aber noch nicht mit
* dem Socket Server verbinden, da dieser noch nicht läuft
*/
var myBot = new bot()
// Hier erstellen wir den Server für den Websocket
var wss = new WSS({
httpServer: server,
autoAcceptConnections: false
})
// Abspeichern der Verbindungen
var connections = {}
// Wenn Sich ein client Socket mit dem Server verbinden will kommt er hier an
wss.on('request', function (request) {
var connection = request.accept('chat', request.origin)
connection.on('message', function (message) {
var name = ''
for (var key in connections) {
if (connection === connections[key]) {
name = key
}
}
var data = JSON.parse(message.utf8Data)
var msg = 'leer'
// Variablen um später den letzten Satz und den Sender zu speichern
var uname
var utype
var umsg
switch (data.type) {
case 'join':
// Wenn der Typ join ist füge ich den Client einfach unserer Liste hinzu
connections[data.name] = connection
msg = '{"type": "join", "names": ["' + Object.keys(connections).join('","') + '"]}'
if (myBot.connected === false) {
myBot.connect()
}
break
case 'msg':
// Erstelle eine Nachricht in JSON mit Typ, Sender und Inhalt
msg = '{"type": "msg", "name": "' + name + '", "msg":"' + data.msg + '"}'
utype = 'msg'
uname = name
umsg = data.msg
break
}
// Sende alle daten an alle verbundenen Sockets
for (var key in connections) {
if (connections[key] && connections[key].send) {
connections[key].send(msg)
}
}
// Leite die Daten des Users an den Bot weiter, damit der antworten kann
if (uname !== 'Raspi-Bot' && utype === 'msg') {
var test = myBot.post(umsg,uname)
}
})
})

18
wissensbasis.json Normal file
View File

@ -0,0 +1,18 @@
{ "intents" :[
{"intent":"kaufen", "answers":["kaufen answer 1","kaufen answer 1","kaufen answer 1"], "repeat1": ["kaufen repeat 1"],"repeat2":["kaufen repeat 2"]},
{"intent":"netzwerk", "answers":["netzwerk answer 1","netzwerk answer 2","netzwerk answer 3"], "repeat1": ["netzwerk repeat 1"],"repeat2":["netzwerk repeat 2"]},
{"intent":"usb", "answers":["usb answer 1","usb answer 2","usb answer 3"], "repeat1": ["usb repeat 1"],"repeat2":["usb repeat 2"]},
{"intent":"display", "answers":["display answer 1","display answer 2","display answer 3"], "repeat1": ["display repeat 1"],"repeat2":["display repeat 2"]},
{"intent":"strom", "answers":["strom answer 1","strom answer 2","strom answer 3"], "repeat1": ["strom repeat 1"],"repeat2":["strom repeat 2"]},
{"intent":"ram", "answers":["ram answer 1","ram answer 2","ram answer 3"], "repeat1": ["ram repeat 1"],"repeat2":["ram repeat 2"]},
{"intent":"cpu", "answers":["cpu answer 1","cpu answer 2","cpu answer 3"], "repeat1": ["cpu repeat 1"],"repeat2":["cpu repeat 2"]},
{"intent":"gpio", "answers":["gpio answer 1","gpio answer 2","gpio answer 3"], "repeat1": ["gpio repeat 1"],"repeat2":["gpio repeat 2"]},
{"intent":"sd", "answers":["sd answer 1","sd answer 2","sd answer 3"], "repeat1": ["sd repeat 1"],"repeat2":["sd repeat 2"]},
{"intent":"os", "answers":["os answer 1","os answer 2","os answer 3"], "repeat1": ["os repeat 1"],"repeat2":["os repeat 2"]}
],
"fallbacks": [
"fallback 1",
"fallback 2",
"fallback 3"
]
}