Skip to content

Commit bfcd59d

Browse files
author
Abdulhakim Ajetunmobi
authored
Merge pull request #7 from nexmo-community/update
Update repo
2 parents 964e03a + 74d6097 commit bfcd59d

File tree

9 files changed

+1312
-66
lines changed

9 files changed

+1312
-66
lines changed

.env-example

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
BASE_URL=
2+
SOCKET_BASE_URL=
3+
VONAGE_APPLICATION_ID=
4+
TEST_HANDSET=
5+
VONAGE_FROM_NUMBER=
6+
TONE_ANALYZER_API_KEY=
7+
TONE_ANALYZER_URL=
8+
TRANSCRIBER_API_KEY=
9+
TRANSCRIBER_SERVER_LOCATION=
10+
TRANSCRIBER_SERVER_INSTANCE_ID=

README.md

Lines changed: 51 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -1,32 +1,60 @@
1-
Sentiment analysis of voice calls using IBM Watson
2-
==================================================
1+
# Sentiment analysis of voice calls using IBM Watson
2+
<img src="https://developer.nexmo.com/assets/images/Vonage_Nexmo.svg" height="48px" alt="Nexmo is now known as Vonage" />
33

4-
This is the companion code for the ["Add Sentiment Analysis to Your Inbound Call Flow with IBM Watson and Nexmo" webinar](https://attendee.gotowebinar.com/recording/7952180850491069704). Please view the webinar
5-
recording for full details on how to run this example.
4+
This is the companion code for the ["Add Sentiment Analysis to Your Inbound Call Flow with IBM Watson and Nexmo" webinar](https://youtu.be/nFIj8RVy8Pg). Note that the project has since been updated since the webinar has been recorded. The main differences are with the authentication with IBM Watson.
65

7-
Quickstart
8-
----------
6+
## Welcome to Vonage
97

10-
There are several environment variables in `call_objects.py` and `ws_server.py` which should be
11-
set to the correct values in your environment.
8+
If you're new to Vonage, you can [sign up for a Vonage API account](https://dashboard.nexmo.com/sign-up?utm_source=DEV_REL) and get some free credit to get you started.
129

13-
NEXMO_APPLICATION_ID
14-
TEST_HANDSET
15-
NEXMO_FROM_NUMBER
16-
TONE_ANALYZER_USERNAME
17-
TONE_ANALYZER_PASSWORD
18-
TRANSCRIBER_USERNAME
19-
TRANSCRIBER_PASSWORD
10+
## IBM Watson Credentials
2011

21-
You should also update all urls within `call_objects.py` to point to your ngrok address.
12+
This project uses 2 IBM Cloud services, the Speech to Text and Tone Analyzer. The credentials you need for Speech to Text are your service's location, e.g. "eu-gb", the service's instance id and your API Key.
2213

23-
# This code has been tested with Python 3.6.3
24-
# Install dependencies
25-
pip install -r requirements.txt
14+
For the Tone Analyzer you need your service's URL and API Key, which is different from the Speech to Text one. They can be fetched from the respective parts of the IBM Cloud dashboard.
2615

27-
# Run Hug server
28-
hug -f call_objects.py
16+
## Running the project
2917

30-
# Run Tornado server
31-
python ws_server.py
18+
First install the project dependencies using `pip install -r requirements.txt`.
3219

20+
Next set up your environment variables. Make an `.env` file using the command `cp .env-example .env`. Then fill out your `VONAGE_APPLICATION_ID`, `TEST_HANDSET`, `VONAGE_FROM_NUMBER` and the IBM Watson credentials. `TEST_HANDSET` is the number of a second phone that will be the moderator of the conference call.
21+
22+
The project has three components:
23+
24+
+ The Vonage Application Server
25+
+ The WebSocket Server
26+
+ The Dashboard
27+
28+
### The Vonage Application Server
29+
30+
Create a localtunnel for the Vonage Application Server port, 8000, using the following command. Replace `SUBDOMAIN` with a unique string
31+
32+
`npx localtunnel --port 8000 --subdomain=SUBDOMAIN`
33+
34+
Add the domain to your `.env` file under `BASE_URL` and update your Vonage Application `answer_url` to the domain. Then you can run the server with `hug -f call_objects.py`.
35+
36+
### WebSocket Server
37+
38+
Create a localtunnel for the WebSocket Server port, 3000, using the following command. Replace `SUBDOMAIN` with the same unique string from the previous step
39+
40+
`npx localtunnel --port 3000 --subdomain=SUBDOMAIN-sockets`
41+
42+
Add the domain to your `.env` file under `SOCKET_BASE_URL`. Then you can run the server with `python3 ws_server.py`.
43+
44+
### The Dashboard
45+
46+
Update `/dashboard/script.js` with the `SOCKET_BASE_URL` then open `index.html` in your web browser. With the dashboard open, you can now make a call to your `VONAGE_FROM_NUMBER` which will call the `TEST_HANDSET` number and open the WebSocket. When the test handset picks up, the call will begin and you will start to see the sentiment analysis on the chart.
47+
48+
![dashboard example](dashboard/example.png)
49+
50+
## Getting Help
51+
52+
We love to hear from you so if you have questions, comments or find a bug in the project, let us know! You can either:
53+
54+
* Open an issue on this repository
55+
* Tweet at us! We're [@VonageDev on Twitter](https://twitter.com/VonageDev)
56+
* Or [join the Vonage Developer Community Slack](https://developer.nexmo.com/community/slack)
57+
58+
## Further Reading
59+
60+
* Check out the Developer Documentation at <https://developer.nexmo.com>

call_objects.py

Lines changed: 20 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,39 @@
11
import os
22
import uuid
33
import hug
4-
import nexmo
4+
import vonage
5+
from dotenv import load_dotenv
6+
from typing import List
57

8+
load_dotenv()
69

710
class CallObjectServer():
811

912
def __init__(self):
1013
self.conversation_name = str(uuid.uuid4())
11-
self.nexmo_client = nexmo.Client(
12-
application_id=os.environ['NEXMO_APPLICATION_ID'],
14+
self.vonage_client = vonage.Client(
15+
application_id=os.getenv('VONAGE_APPLICATION_ID'),
1316
private_key='private.key'
1417
)
1518

16-
def start(self):
17-
18-
self.nexmo_client.create_call({
19-
'to': [{'type': 'phone', 'number': os.environ['TEST_HANDSET']}],
20-
'from': {'type': 'phone', 'number': os.environ['NEXMO_FROM_NUMBER']},
21-
'answer_url': ['https://nexmo-sentiment.ngrok.io/moderator']
19+
def start(self) -> List[dict]:
20+
self.vonage_client.create_call({
21+
'to': [{'type': 'phone', 'number': os.getenv('TEST_HANDSET')}],
22+
'from': {'type': 'phone', 'number': os.getenv('VONAGE_FROM_NUMBER')},
23+
'answer_url': ['https://' + os.getenv('BASE_URL') + '/moderator']
2224
})
2325

24-
self.ws_call = self.nexmo_client.create_call({
26+
self.ws_call = self.vonage_client.create_call({
2527
'to': [
2628
{
2729
"type": "websocket",
28-
"uri": "ws://nexmo-sentiment-sockets.ngrok.io/audio",
30+
"uri": "ws://" + os.getenv('SOCKET_BASE_URL') + "/audio",
2931
"content-type": "audio/l16;rate=16000",
3032
"headers": {}
3133
}
3234
],
33-
'from': {'type': 'phone', 'number': os.environ['NEXMO_FROM_NUMBER']},
34-
'answer_url': ['https://nexmo-sentiment.ngrok.io/attendee']
35+
'from': {'type': 'phone', 'number': os.getenv('VONAGE_FROM_NUMBER')},
36+
'answer_url': ['https://' + os.getenv('BASE_URL') + '/attendee']
3537
})
3638

3739
return [
@@ -43,11 +45,11 @@ def start(self):
4345
"action": "conversation",
4446
"name": self.conversation_name,
4547
"startOnEnter": "false",
46-
"musicOnHoldUrl": ["https://nexmo-sentiment.ngrok.io/hold.mp3"]
48+
"musicOnHoldUrl": ["https://" + os.getenv('BASE_URL') + "/hold.mp3"]
4749
}
4850
]
4951

50-
def moderator(self):
52+
def moderator(self) -> List[dict]:
5153
return [
5254
{
5355
"action": "conversation",
@@ -57,13 +59,13 @@ def moderator(self):
5759
}
5860
]
5961

60-
def attendee(self):
62+
def attendee(self) -> List[dict]:
6163
return [
6264
{
6365
"action": "conversation",
6466
"name": self.conversation_name,
6567
"startOnEnter": "false",
66-
"musicOnHoldUrl": ["https://nexmo-sentiment.ngrok.io/hold.mp3"]
68+
"musicOnHoldUrl": ["https://" + os.getenv('BASE_URL') + "/hold.mp3"]
6769
}
6870
]
6971

@@ -77,4 +79,4 @@ def static(self):
7779
router.get('/')(server.start)
7880
router.get('/hold.mp3', output=hug.output_format.file)(server.static)
7981
router.get('/moderator')(server.moderator)
80-
router.get('/attendee')(server.attendee)
82+
router.get('/attendee')(server.attendee)

dashboard/example.png

64.5 KB
Loading

dashboard/index.html

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="utf-8" />
5+
<meta name="viewport" content="width=device-width, initial-scale=1" />
6+
<title>Sentiment</title>
7+
<script type="text/javascript" src="script.js" defer></script>
8+
<script type="text/javascript" src="smoothie.js"></script>
9+
</head>
10+
11+
<body>
12+
<canvas id="mycanvas" width="900" height="300"></canvas>
13+
14+
<ul id="toneList">
15+
</ul>
16+
</body>
17+
</html>

dashboard/script.js

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
var smoothie = new SmoothieChart({ maxValue: 1, minValue: 0 });
2+
smoothie.streamTo(document.getElementById("mycanvas"), 3000);
3+
4+
const ws = new WebSocket("wss://SOCKET_BASE_URL/dashboard");
5+
6+
var series = {};
7+
8+
ws.onmessage = function(evt) {
9+
const tones = JSON.parse(evt.data);
10+
11+
tones.forEach(function(tone) {
12+
if (series[tone.tone_id]) {
13+
addNewEntry(series[tone.tone_id], tone.score);
14+
} else {
15+
const toneId = tone.tone_id;
16+
const color = random_rgb();
17+
const newEntry = {
18+
timeSeries: new TimeSeries(),
19+
stroke: color,
20+
fill: "rgba(255,255,255,0.7)"
21+
};
22+
23+
series[toneId] = newEntry;
24+
addSeriesToChart(newEntry);
25+
addToList(toneId, color);
26+
}
27+
});
28+
};
29+
30+
function addToList(toneId, color) {
31+
var list = document.getElementById("toneList");
32+
var entry = document.createElement("li");
33+
entry.appendChild(document.createTextNode(toneId));
34+
entry.style.color = color
35+
list.appendChild(entry);
36+
}
37+
38+
function addSeriesToChart(entry) {
39+
smoothie.addTimeSeries(entry.timeSeries, {
40+
strokeStyle: entry.stroke,
41+
fillStyle: entry.fill,
42+
lineWidth: 6
43+
});
44+
}
45+
46+
function addNewEntry(entry, score) {
47+
entry.timeSeries.append(new Date().getTime(), score);
48+
}
49+
50+
function random_rgb() {
51+
var o = Math.round,
52+
r = Math.random,
53+
s = 255;
54+
return "rgba(" + o(r() * s) + "," + o(r() * s) + "," + o(r() * s) + ")";
55+
}

0 commit comments

Comments
 (0)