Open avorozheev opened 5 years ago
If you wish to upload files you may need to redesign your interface and implement the upload function your self using javascript.
Basically, the driver supports it but the original interface does not provide for it.
The implementation in javascript looks like this:
var form = new FormData();
form.append("driver", "web");
form.append("attachment", "file");
form.append("file", "");
var settings = {
"url": "http://localhost:500",
"method": "POST",
"timeout": 0,
"processData": false,
"mimeType": "multipart/form-data",
"contentType": false,
"data": form
};
$.ajax(settings).done(function (response) {
console.log(response);
});
Explanation:
driver
parameter must be specified (web)attachment
parameter could be any of the four possible types (audio | video | location | file)file
@claretnnamocha where do I have to add this code? Into which file?
It could be in a file or script that handles the upload event on your interface @menno-dev
@claretnnamocha mhm.. unfortunately I have no clue. Where could it be within Botman Studio? Thank you very much!
It could be in a file or script that handles the upload event on your interface @menno-dev
This upload code is written on the front-end not in the back-end
@claretnnamocha could you help me with an example of implementation? do I have to copy paste your code in my front end and that all? which parameter do I have to change? do you have any working example that will help me please?
@avorozheev have you finaly found a solution? @menno-dev have you found a solution? Thank you very much for your help
Hi, any news on this issue? I am currently trying to implement this solution but don´t know where should I put this code or if something is missing. Thanks!
Just wondering if there is any more help with this issue as I am having the same dilemma uploading images via the web widget.
There is a way. It's entirely a front-end issue, on the back-end you can listen for images, video, and attachments in general however you want to. but from the front-end here's how things work (at least for me) I use the botman widget by using two js script files on the front-end, A widget.js file on the main page, and a chat.js on a second page. The second page is the page the the frameEndpoint property of the botmanWidget object (The botmanWidget object remember is the object used to configure certain properties of botman from the main page) now in that page that the frameEndpoint property targets, That is where changes to the interface can be implemented here's how the page looks from my point of view
<html>
<head>
<title>Widget Body</title>
<!-- <link rel="stylesheet" type="text/css" href="static/css/chat.min.css"> -->
<link href="static/bootstrap-4.1.3/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" type="text/css" href="static/css/font-awesome-4.7.0/css/font-awesome.css">
<link rel="stylesheet" type="text/css" href="static/css/chat.css">
<link rel="stylesheet" type="text/css" href="static/css/styles_attachment.css">
</head>
<body>
<script src="static/js/jquery-1.10.2.min.js"></script>
<script src="static/bootstrap-4.1.3/js/bootstrap.min.js"></script>
<script id="botmanWidget" src='static/js/chat.js?v=1'></script>
<script src="static/js/chat_changes.js?v=1"></script>
<div id="fileApp"></div>
<div class="div-attachments-container">
<div class="div-attachments">
<span id="view-audio" class="view-attachment-left fa fa-file-audio-o"></span>
<span id="view-video" class="view-attachment-left fa fa-file-video-o"></span>
<span id="view-file-name" class="view-attachment-left"></span>
<span id="send" class="view-attachment-right fa fa-paper-plane" en="true"></span>
</div>
</div>
<script src='static/js/bot_attachment.js?v=1'></script>
</body>
</html>
Two script files do the most of my magic for me, the chat_changes.js (This file helps me adjust the appearance of changes I make to the interface) and bot_attachment.js (Here I upload the selected file to the server) files
Here's what my bot_attachment.js file looks like
$(document).ready(function () {
document.getElementById('fileApp').innerHTML = `
<div>
<input style="display:none" type="file" id="fileInput" />
</div>
`;
const fileInput = document.querySelector("#fileInput");
var file_type;
var files;
$("#view-video").on("click", function(e){
file_type = "video";
fileInput.click();
});
$("#view-audio").on("click", function(e){
file_type = "audio";
fileInput.click();
});
$("#send").on("click", function(e){
if(($("#view-file-name").text() == "") || (files == null)) return;
sendFile(files[0], file_type);
});
$("#fileInput").on("change", function(e){
console.log("File here");
files = e.target.files;
console.log(files);
if (files.length > 0) {
$("#view-file-name").text(files[0]["name"]);
}
});
function sendFile(file, filetype){
var form = new FormData();
form.append("driver", "web");
form.append("attachment", filetype);
form.append("interactive", 0);
form.append("file", file);
form.append("userId", "replace with 0 or your preferred value");
var settings = {
"url": "https://chatbot/server",
"method": "POST",
"timeout": 0,
"processData": false,
"mimeType": "multipart/form-data",
"contentType": false,
"data": form
};
$.ajax(settings).done(function (response) {
files = null;
$("#fileInput").val(null);
$("#view-file-name").text("");
window.parent.postMessage(response, '*');
});
}
});
here's what my chat_changes.js file looks like
window.addEventListener('load', function () {
var messageArea = document.getElementById("messageArea");
var userText = document.getElementById("userText");
var chatOl = document.getElementsByClassName("chat")[0];
var messageAreaHeight = messageArea.clientHeight;
chatHeight = chatOl.clientHeight;
messageArea.style.height = (messageAreaHeight - 20) + "px";
chatOl.style.height = (chatHeight - 20) + "px";
userText.setAttribute("autocomplete","off");
userText.style.position = "absolute";
userText.style.bottom = "40px";
});
now so far, this allows you send files to the server as long as you have the
$botman->receivesVideos(function($bot, $videos){
//some code to run here for receiving videos
});
or
$botman->receivesAudio(function($bot, $videos){
//some code to run here for receiving audios
});
similar functions exists for files and images and whatever now all this makes sure the server works with receiving files, but the servers response doesn't get back to your application the way it is wired up by the botman widget by default anymore, here where you get a bit creative. The botman widget on the main page exposes and object window.botmanChatWidget This widget has an api writeToMessages, which allows us write replies from our server.
if you notice in the bot_attachment I use the postMessage method to post a a message from the iframe back to my main page I use that action to trigger a function, now on the main page in a file I called botmanTest.js the function is handleMessage and here's what the script looks like
here's what the code looks like, in a script file I named botmanTest.js (you can use whatever name of course)
$(document).ready(function() {
const botmanInterval = setInterval(checkBotman, 1000);
function checkBotman(){
if(window.botmanChatWidget != "undefined"){
clearInterval(botmanInterval);
}
}
if(!window.addEventListener){
// IE8 support
window.attachEvent('onmessage', handleMessage);
} else {
window.addEventListener('message', handleMessage, false);
}
function handleMessage(event){
if(event.origin == ''){
//just some code to constrain the origin if I need to
}
var json_response = JSON.parse(event.data);
(json_response.messages || []).forEach(function (t) {
window.botmanChatWidget.writeToMessages(t);
});
}
});
i use a tacky method to make sure I'm not using the botmanChatWidet object before it is defined. Documentation for botman web widget isn't all so well set up. Hopefully this would help someone
Here's the stylesheet code for the styles_attachment.css files
.view-attachment-left { z-index: 999999999999999999; padding-left: 20px; cursor: pointer; } .view-attachment-right { z-index: 999999999999999999; padding-right: 10px; display: inline-block; cursor: pointer; position: absolute; right: 0px; } .div-attachments-container { height: 30px; width: 100%; display: inline-block; position: fixed; bottom: 8px; } .div-attachments { margin-top: 10px; position: relative; }
it is possible to use above code inside conversations? i try to catch an image inside conversation and it seems that image is sended directly to routes/botman.php in my fallback function.
it is possible to use above code inside conversations? i try to catch an image inside conversation and it seems that image is sended directly to routes/botman.php in my fallback function.
The problem is with the userId in the form, make sure the right id is being passed there, I had issues with that too
form.append("userId", make very sure the right userId is appearing here, else the default response gets activated like there is no on going conversation)
Just make sure the right userId is being passed in there, it should work fine afterwards. I Encountered the same problem too, took me a while to figure it out
shdi
the code u have refere is not working
Hi!
It took me some time, but I found what I believe is a nice way to do this without having to modify the library. Basically, the idea is to inject the FileUpload
HTML/JS code directly into the iframe
. Then, use a MutationObserver
to react on the bot responses asking for file uploads to display the user with the FileUpload
component instead of the textarea
.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Laravel</title>
</head>
<body>
<!-- YOUR HTML HERE -->
</body>
<script>
const userId = (Math.random() + 1).toString(36).substring(7);
const botmanInterval = setInterval(checkBotman, 1000);
function checkBotman() {
const elChatBotManFrame = document.getElementById('chatBotManFrame');
if (elChatBotManFrame) {
const elChatWidget = elChatBotManFrame.contentWindow.document.getElementById('botmanChatRoot');
const elMessageArea = elChatBotManFrame.contentWindow.document.getElementById('messageArea');
const elTextInput = elChatBotManFrame.contentWindow.document.getElementById('userText');
if (!elChatWidget || !elMessageArea || !elTextInput) {
return;
}
clearInterval(botmanInterval);
// Append the file upload component to the DOM
const elFileInputContainer = document.createElement('div');
elFileInputContainer.innerHTML = `
<style>
.gg-software-upload {
box-sizing: border-box;
position: relative;
display: block;
transform: scale(var(--ggs,1));
width: 16px;
height: 6px;
border: 2px solid;
border-top: 0;
border-bottom-left-radius: 2px;
border-bottom-right-radius: 2px;
margin-top: 8px
}
.gg-software-upload::after {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
width: 8px;
height: 8px;
border-left: 2px solid;
border-top: 2px solid;
transform: rotate(45deg);
left: 2px;
bottom: 4px
}
.gg-software-upload::before {
content: "";
display: block;
box-sizing: border-box;
position: absolute;
border-radius: 3px;
width: 2px;
height: 10px;
background: currentColor;
left: 5px;
bottom: 3px
}
.hidden {
display: none !important;
}
.file-input {
width: 92%;
position: fixed;
display: flex;
bottom: 0;
padding: 15px;
background: #ffffff;
box-shadow: 0 -6px 12px 0 rgba(235,235,235,.95);
}
.file-input input {
width: 90%;
overflow: hidden;
}
.disabled {
display: none;
}
.file-upload-btn {
align-self: center;
margin-left: auto;
cursor: pointer;
}
</style>
<div class="file-input hidden">
<input type="file" name="file" id="file" />
<i class="gg-software-upload file-upload-btn disabled"></i>
</div>
`;
elChatWidget.append(elFileInputContainer);
const elFileUploadBtn = elChatBotManFrame.contentWindow.document.getElementsByClassName('file-upload-btn')[0];
const elFileInput = elChatBotManFrame.contentWindow.document.getElementsByClassName('file-input')[0];
const elFile = elChatBotManFrame.contentWindow.document.getElementById('file');
const selection = [];
elFile.addEventListener('change', event => {
const files = event.target.files;
if (files.length > 0) {
selection.push(files); // push the whole FileList
elFileUploadBtn.classList.toggle('disabled'); // show upload button
}
});
elFileUploadBtn.addEventListener('click', event => {
for (let i=0; i<selection.length; i++) {
for (let j=0; j<selection[i].length; j++) {
const file = selection[i][j];
const filename = file['name'];
const form = new FormData();
form.append("driver", "web");
form.append("attachment", "file"); // audio | video | location | file
form.append("interactive", "0");
form.append("file", file);
form.append("userId", userId);
const options = {
method: 'POST',
body: form,
};
fetch(window.location.origin + "/botman", options).then(response => {
if (response.status === 200) {
window.botmanChatWidget.sayAsBot('Your file ' + filename + ' has been sent :-)');
} else {
window.botmanChatWidget.sayAsBot('Your file ' + filename + ' could not be sent :-(');
}
});
}
}
selection.length = 0; // remove selected files
elFile.value = ""; // reset file-input
elFileInput.classList.toggle('hidden'); // hide file-input
elFileUploadBtn.classList.toggle('disabled'); // hide upload button
elTextInput.classList.toggle('hidden'); // display text area
elTextInput.focus(); // set focus on text area
});
// Observe incoming messages and react accordingly
const observer = new MutationObserver(mutations => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(addedNode => {
// If the bot asks for a file upload, display the file input instead of the text area
if (addedNode.classList.contains('chatbot')) {
const elMessage = addedNode.getElementsByTagName('p')[0].innerText;
if (/.*(upload|téléverser?).*/i.test(elMessage)) {
elTextInput.classList.toggle('hidden');
elFileInput.classList.toggle('hidden');
}
}
});
});
});
const elChatArea = elMessageArea.getElementsByClassName('chat')[0];
observer.observe(elChatArea, { subtree: false, childList: true });
}
}
</script>
<link rel="stylesheet"
type="text/css"
href="https://cdn.jsdelivr.net/npm/botman-web-widget@0/build/assets/css/chat.min.css">
<script>
window.botmanWidget = {
title: 'BotMan',
aboutText: 'Powered by ComputableFacts',
aboutLink: 'https://computablefacts.com',
userId: userId,
};
</script>
<script src='https://cdn.jsdelivr.net/npm/botman-web-widget@0/build/js/widget.js'></script>
</html>
On the server side, I have the following code in AppServiceProvider.php
:
<?php
namespace App\Providers;
use Illuminate\Foundation\Http\Middleware\VerifyCsrfToken;
use Illuminate\Support\ServiceProvider;
class AppServiceProvider extends ServiceProvider
{
/**
* Register any application services.
*/
public function register(): void
{
//
}
/**
* Bootstrap any application services.
*/
public function boot(): void
{
VerifyCsrfToken::except(['botman']);
}
}
I have the following code in routes/web.php
:
<?php
use App\Http\Controllers\BotManController;
use Illuminate\Support\Facades\Route;
Route::get('/', function () {
return view('welcome');
});
Route::match(['get', 'post'], '/botman', [BotManController::class, 'handle']);
I have the following code in BotManController.php
:
<?php
namespace App\Http\Controllers;
use BotMan\BotMan\BotMan;
use BotMan\BotMan\Messages\Incoming\Answer;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Str;
class BotManController extends Controller
{
public function handle(): void
{
$botman = app('botman');
$botman->receivesFiles(function (BotMan $botman, $files) {
});
$botman->hears('.*(hi|hello|bonjour).*', function (BotMan $botman, string $message) {
if (Str::lower($message) === 'hi' || Str::lower($message) === 'hello') {
$this->askNameEn($botman);
} else if (Str::lower($message) === 'bonjour') {
$this->askNameFr($botman);
}
});
$botman->fallback(function (BotMan $botman) {
$botman->reply('Sorry, I did not understand these commands.');
});
$botman->listen();
}
private function askNameEn(BotMan $botman): void
{
$botman->ask('Hello! What is your name?', function (Answer $answer) use ($botman) {
$name = $answer->getText();
$this->say("Nice to meet you {$name}!");
$this->askForFiles('I am ready now. Upload your file!', function ($files) {
foreach ($files as $file) {
$url = $file->getUrl();
$payload = $file->getPayload();
Log::debug($url);
}
});
});
}
private function askNameFr(BotMan $botman): void
{
$botman->ask('Bonjour! Quel est ton nom?', function (Answer $answer) use ($botman) {
$name = $answer->getText();
$this->say("Enchanté, {$name}!");
$this->askForFiles('Je suis prêt maintenant. Téléverse ton fichier!', function ($files) {
foreach ($files as $file) {
$url = $file->getUrl();
$payload = $file->getPayload();
Log::debug($url);
}
});
});
}
}
@csavelief, thanks a lot! The code works really well. However, can the code be modified to send multiple files?
I'm using Botman WebWidget implementation from tutorial here: https://botman.io/2.0/driver-web
But it doesn't seem to have an attachment functionality: neither button, nor drag-n-drop into the chat window. Also surfed in documentation and related issues - no even mention of attachment functionality in web widget.
Am I doing something wrong, or it is really not implemented?