Let's get started on our agent UI. Assuming you've followed the conventions so far in this tutorial, the UI we create will be accessible using your web browser at:
http://localhost:8080/agents?WorkerSid=WK01234012340123401234
(substitute your Alice's WorkerSid)
We pass the WorkerSid in the URL to avoid implementing complex user management in our demo. In reality, you are likely to store a user's WorkerSid in your database alongside other User attributes.
Let's add on our to our run.py
file to add an endpoint to generate a page based on a template.
1# -*- coding: latin-1 -*-23from flask import Flask, request, Response, render_template4from twilio.rest import Client5from twilio.jwt.taskrouter.capabilities import WorkerCapabilityToken6from twilio.twiml.voice_response import VoiceResponse78app = Flask(__name__)910# Your Account Sid and Auth Token from twilio.com/user/account11account_sid = "{{ account_sid }}"12auth_token = "{{ auth_token }}"13workspace_sid = "{{ workspace_sid }}"14workflow_sid = "{{ workflow_sid }}"1516client = Client(account_sid, auth_token)1718@app.route("/assignment_callback", methods=['GET', 'POST'])19def assignment_callback():20"""Respond to assignment callbacks with an acceptance and 200 response"""2122ret = '{"instruction": "dequeue", "from"="+15556667777"}' # a verified phone number from your twilio account23resp = Response(response=ret, status=200, mimetype='application/json')24return resp2526@app.route("/create_task", methods=['GET', 'POST'])27def create_task():28"""Creating a Task"""29task = client.taskrouter.workspaces(workspace_sid) \30.tasks.create(workflow_sid=workflow_sid, attributes='{"type":"support"}')3132print(task.attributes)33resp = Response({}, status=200, mimetype='application/json')34return resp3536@app.route("/accept_reservation", methods=['GET', 'POST'])37def accept_reservation():38"""Accepting a Reservation"""39task_sid = request.args.get('task_sid')40reservation_sid = request.args.get('reservation_sid')4142reservation = client.taskrouter.workspaces(workspace_sid) \43.tasks(task_sid) \44.reservations(reservation_sid) \45.update(reservation_status='accepted')4647print(reservation.reservation_status)48print(reservation.worker_name)4950resp = Response({}, status=200, mimetype='application/json')51return resp5253@app.route("/incoming_call", methods=['GET', 'POST'])54def incoming_call():55"""Respond to incoming requests."""5657resp = VoiceResponse()58with resp.gather(numDigits=1, action="/enqueue_call", method="POST", timeout=5) as g:59g.say("Para Español oprime el uno.".decode("utf8"), language='es')60g.say("For English, please hold or press two.", language='en')6162return str(resp)6364@app.route("/enqueue_call", methods=['GET', 'POST'])65def enqueue_call():66digit_pressed = request.args.get('Digits')67if digit_pressed == 1 :68language = "es"69else:70language = "en"7172resp = VoiceResponse()73with resp.enqueue(None, workflowSid=workflow_sid) as e:74e.task('{"selected_language":"' + language + '"}')7576return str(resp)7778@app.route("/agents", methods=['GET'])79def generate_view():80worker_sid = request.args.get('WorkerSid')8182worker_capability = WorkerCapabilityToken(account_sid, auth_token, workspace_sid, worker_sid)83worker_capability.allow_update_activities()84worker_capability.allow_update_reservations()8586worker_token = worker_capability.to_jwt().decode('utf-8')8788return render_template('agent.html', worker_token=worker_token)8990if __name__ == "__main__":91app.run(debug=True)
Now create a folder called templates. Inside that folder, create a template file that will be rendered when the URL is requested:
1<!DOCTYPE html>2<html>3<head>4<title>Customer Care - Voice Agent Screen</title>5<link rel="stylesheet" href="//media.twiliocdn.com/taskrouter/quickstart/agent.css"/>6<script src="https://sdk.twilio.com/js/taskrouter/v1.21/taskrouter.min.js" integrity="sha384-5fq+0qjayReAreRyHy38VpD3Gr9R2OYIzonwIkoGI4M9dhfKW6RWeRnZjfwSrpN8" crossorigin="anonymous"></script>7<script type="text/javascript">8/* Subscribe to a subset of the available TaskRouter.js events for a worker */9function registerTaskRouterCallbacks() {10worker.on('ready', function(worker) {11agentActivityChanged(worker.activityName);12logger("Successfully registered as: " + worker.friendlyName)13logger("Current activity is: " + worker.activityName);14});1516worker.on('activity.update', function(worker) {17agentActivityChanged(worker.activityName);18logger("Worker activity changed to: " + worker.activityName);19});2021worker.on("reservation.created", function(reservation) {22logger("-----");23logger("You have been reserved to handle a call!");24logger("Call from: " + reservation.task.attributes.from);25logger("Selected language: " + reservation.task.attributes.selected_language);26logger("-----");27});2829worker.on("reservation.accepted", function(reservation) {30logger("Reservation " + reservation.sid + " accepted!");31});3233worker.on("reservation.rejected", function(reservation) {34logger("Reservation " + reservation.sid + " rejected!");35});3637worker.on("reservation.timeout", function(reservation) {38logger("Reservation " + reservation.sid + " timed out!");39});4041worker.on("reservation.canceled", function(reservation) {42logger("Reservation " + reservation.sid + " canceled!");43});44}4546/* Hook up the agent Activity buttons to TaskRouter.js */4748function bindAgentActivityButtons() {49// Fetch the full list of available Activities from TaskRouter. Store each50// ActivitySid against the matching Friendly Name51var activitySids = {};52worker.activities.fetch(function(error, activityList) {53var activities = activityList.data;54var i = activities.length;55while (i--) {56activitySids[activities[i].friendlyName] = activities[i].sid;57}58});5960/* For each button of class 'change-activity' in our Agent UI, look up the61ActivitySid corresponding to the Friendly Name in the button's next-activity62data attribute. Use Worker.js to transition the agent to that ActivitySid63when the button is clicked.*/64var elements = document.getElementsByClassName('change-activity');65var i = elements.length;66while (i--) {67elements[i].onclick = function() {68var nextActivity = this.dataset.nextActivity;69var nextActivitySid = activitySids[nextActivity];70worker.update({"ActivitySid":nextActivitySid});71}72}73}7475/* Update the UI to reflect a change in Activity */7677function agentActivityChanged(activity) {78hideAgentActivities();79showAgentActivity(activity);80}8182function hideAgentActivities() {83var elements = document.getElementsByClassName('agent-activity');84var i = elements.length;85while (i--) {86elements[i].style.display = 'none';87}88}8990function showAgentActivity(activity) {91activity = activity.toLowerCase();92var elements = document.getElementsByClassName(('agent-activity ' + activity));93elements.item(0).style.display = 'block';94}9596/* Other stuff */9798function logger(message) {99var log = document.getElementById('log');100log.value += "\n> " + message;101log.scrollTop = log.scrollHeight;102}103104window.onload = function() {105// Initialize TaskRouter.js on page load using window.workerToken -106// a Twilio Capability token that was set from rendering the template with agents endpoint107logger("Initializing...");108window.worker = new Twilio.TaskRouter.Worker("{{ worker_token }}");109110registerTaskRouterCallbacks();111bindAgentActivityButtons();112};113</script>114</head>115<body>116<div class="content">117<section class="agent-activity offline">118<p class="activity">Offline</p>119<button class="change-activity" data-next-activity="Idle">Go Available</button>120</section>121<section class="agent-activity idle">122<p class="activity"><span>Available</span></p>123<button class="change-activity" data-next-activity="Offline">Go Offline</button>124</section>125<section class="agent-activity reserved">126<p class="activity">Reserved</p>127</section>128<section class="agent-activity busy">129<p class="activity">Busy</p>130</section>131<section class="agent-activity wrapup">132<p class="activity">Wrap-Up</p>133<button class="change-activity" data-next-activity="Idle">Go Available</button>134<button class="change-activity" data-next-activity="Offline">Go Offline</button>135</section>136<section class="log">137<textarea id="log" readonly="true"></textarea>138</section>139</div>140</body>141</html>
You'll notice that we included two external files:
And that's it! Open http://localhost:8080/agents?WorkerSid=WK012340123401234
in your browser and you should see the screen below. If you make the same phone call as we made in Part 3, you should see Alice's Activity transition on screen as she is reserved and assigned to handle the Task.
If you see "Initializing..." and no progress, make sure that you have included the correct WorkerSid in the "WorkerSid" request parameter of the URL.
For more details, refer to the TaskRouter JavaScript SDK documentation.