Initial commit
This commit is contained in:
commit
15a6bcb07f
1
.gitignore
vendored
Normal file
1
.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
||||
node_modules/
|
17
.vscode/launch.json
vendored
Normal file
17
.vscode/launch.json
vendored
Normal 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}"
|
||||
}
|
||||
]
|
||||
}
|
173
bot.js
Normal file
173
bot.js
Normal 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
1129
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
20
package.json
Normal file
20
package.json
Normal 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
153
public/css/newchat.css
Normal file
File diff suppressed because one or more lines are too long
30
public/index.html
Normal file
30
public/index.html
Normal 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
107
public/js/newchat.js
Normal 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
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
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
96
staticExpress.js
Normal 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
18
wissensbasis.json
Normal 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"
|
||||
]
|
||||
}
|
Loading…
Reference in New Issue
Block a user