-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathChatbot.tsx
More file actions
161 lines (145 loc) · 6.28 KB
/
Chatbot.tsx
File metadata and controls
161 lines (145 loc) · 6.28 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
// src/Chatbot.tsx
//////////////// Chatbot Component /////////////////
// This react component embeds the Botpress widget as //
// An iframe inside the page, and also includes all //
// the needed event handlers for receiving messages //
// from the STT, sending them to the bot, and then //
// pushing the bot messages to the TTS service. //
//////////////////////////////////////////////////////////
import React, { useEffect, useRef, useState, useCallback } from 'react';
import './Chatbot.css';
import axios from 'axios'
console.log(process.env)
// Define a type for the input that we'll be tossing around our services
// It also insludes an update for the status on the website
interface ChatbotProps {
userInput: string;
onUpdateStatus: (status: string) => void;
}
// Change this to reflect your bot's information
const botConfig = {
composerPlaceholder: 'Chat with bot',
botConversationDescription: 'This chatbot was built surprisingly fast with Botpress',
botName: 'Survey Bot',
botId: "811258c7-429f-48bf-9dda-1af8c5c8a0d5",
hostUrl: 'https://cdn.botpress.cloud/webchat/v1',
messagingUrl: 'https://messaging.botpress.cloud',
clientId: "811258c7-429f-48bf-9dda-1af8c5c8a0d5",
containerWidth: "100%25", // Needed for bot to take up entire container
layoutWidth: "100%25", // Needed for bot to take up entire container
disableAnimations: true, // Needed for bot to render without its widget
hideWidget: true, // Since we are embedding the bot, no need for a widget
enableConversationDeletion: true,
stylesheet: "https://webchat-styler-css.botpress.app/prod/code/7c2e8673-b084-4bea-849c-1c7addf1eb04/v31111/style.css"
};
const Chatbot: React.FC<ChatbotProps> = ({ userInput, onUpdateStatus }) => {
const iframeRef = useRef<HTMLIFrameElement>(null);
const audioRef = useRef<HTMLAudioElement | null>(null);
const [messageQueue, setMessageQueue] = useState<string[]>([]);
const [audioQueue, setAudioQueue] = useState<string[]>([]);
const [isPlaying, setIsPlaying] = useState(false);
// Sets up the Botpress widget in an iframe when the component mounts.
useEffect(() => {
const iframeContent = `
<body>
<script src='https://cdn.botpress.cloud/webchat/v0/inject.js'></script>
<script>
window.botpressWebChat.init(${JSON.stringify(botConfig)});
window.botpressWebChat.onEvent(function (event) {
if (event.type === 'MESSAGE.RECEIVED') {
console.log('posting message')
parent.postMessage({
type: 'MESSAGE.RECEIVED',
data: event.value.payload
}, '*');
} else if (event.type === 'LIFECYCLE.LOADED') {
window.botpressWebChat.sendEvent({type: 'show'});
}
}, ['MESSAGE.RECEIVED', 'LIFECYCLE.LOADED']);
window.addEventListener('message', (event) => {
if (event.data && event.data.type === 'NEW_TEXT') {
window.botpressWebChat.sendPayload({ type: 'text', text: event.data.text });
}
});
</script>
</body>`;
if (iframeRef.current) {
iframeRef.current.srcdoc = iframeContent;
}
}, []);
// Send user input to the bot
useEffect(() => {
if (userInput && iframeRef.current) {
onUpdateStatus("Chatbot processing...");
iframeRef.current.contentWindow?.postMessage({ type: 'NEW_TEXT', text: userInput }, '*');
}
}, [userInput, onUpdateStatus]);
// Receive bot messages from the web widget
useEffect(() => {
const handleReceivedMessage = (event: MessageEvent) => {
if (event.data && event.data.type === 'MESSAGE.RECEIVED') {
setMessageQueue(prevQueue => [...prevQueue, event.data.data.text]);
}
};
window.addEventListener('message', handleReceivedMessage);
return () => {
window.removeEventListener('message', handleReceivedMessage);
};
}, []);
// Plays audio from the queue
const playNextAudio = useCallback(() => {
if (audioQueue.length === 0 || isPlaying) return;
const next = audioQueue[0];
if (audioRef.current && next) {
setIsPlaying(true);
audioRef.current.src = next;
audioRef.current
.play()
.catch(err => {
console.error('Audio playback error:', err);
setIsPlaying(false);
});
}
}, [audioQueue, isPlaying]);
// Monitors the audio queue and plays audio when there is a change
useEffect(() => {
playNextAudio();
}, [audioQueue, playNextAudio]);
useEffect(() => {
if (messageQueue.length > 0 && !isPlaying) {
const fetchAudio = async () => {
const message = messageQueue.shift();
try {
const response = await axios.post('/api/tts', { text: message }, { responseType: 'blob' });
const blobUrl = URL.createObjectURL(response.data);
setAudioQueue(prevQueue => [...prevQueue, blobUrl]);
} catch (error) {
console.error('Error fetching audio:', error);
}
};
fetchAudio();
}
}, [messageQueue, isPlaying]); // Fetch audio only when not currently playing
useEffect(() => {
if (!audioRef.current) {
const audio = new Audio();
audio.style.display = 'none';
audio.volume = 0.75;
document.body.appendChild(audio);
audioRef.current = audio;
audio.onended = () => {
setIsPlaying(false);
setAudioQueue(q => q.slice(1));
};
}
return () => {
audioRef.current?.remove();
};
}, [audioQueue.length, playNextAudio]);
return (
<div className="chatbot-shell">
<iframe ref={iframeRef} title="Botpress Webchat" />
</div>
);
};
export default Chatbot;