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.
_91# -*- coding: latin-1 -*-_91_91from flask import Flask, request, Response, render_template_91from twilio.rest import Client_91from twilio.jwt.taskrouter.capabilities import WorkerCapabilityToken_91from twilio.twiml.voice_response import VoiceResponse_91_91app = Flask(__name__)_91_91# Your Account Sid and Auth Token from twilio.com/user/account_91account_sid = "{{ account_sid }}"_91auth_token = "{{ auth_token }}"_91workspace_sid = "{{ workspace_sid }}"_91workflow_sid = "{{ workflow_sid }}"_91_91client = Client(account_sid, auth_token)_91_91@app.route("/assignment_callback", methods=['GET', 'POST'])_91def assignment_callback():_91 """Respond to assignment callbacks with an acceptance and 200 response"""_91_91 ret = '{"instruction": "dequeue", "from"="+15556667777"}' # a verified phone number from your twilio account_91 resp = Response(response=ret, status=200, mimetype='application/json')_91 return resp_91_91@app.route("/create_task", methods=['GET', 'POST'])_91def create_task():_91 """Creating a Task"""_91 task = client.taskrouter.workspaces(workspace_sid) \_91 .tasks.create(workflow_sid=workflow_sid, attributes='{"type":"support"}')_91_91 print(task.attributes)_91 resp = Response({}, status=200, mimetype='application/json')_91 return resp_91_91@app.route("/accept_reservation", methods=['GET', 'POST'])_91def accept_reservation():_91 """Accepting a Reservation"""_91 task_sid = request.args.get('task_sid')_91 reservation_sid = request.args.get('reservation_sid')_91_91 reservation = client.taskrouter.workspaces(workspace_sid) \_91 .tasks(task_sid) \_91 .reservations(reservation_sid) \_91 .update(reservation_status='accepted')_91_91 print(reservation.reservation_status)_91 print(reservation.worker_name)_91_91 resp = Response({}, status=200, mimetype='application/json')_91 return resp_91_91@app.route("/incoming_call", methods=['GET', 'POST'])_91def incoming_call():_91 """Respond to incoming requests."""_91_91 resp = VoiceResponse()_91 with resp.gather(numDigits=1, action="/enqueue_call", method="POST", timeout=5) as g:_91 g.say("Para Español oprime el uno.".decode("utf8"), language='es')_91 g.say("For English, please hold or press two.", language='en')_91_91 return str(resp)_91_91@app.route("/enqueue_call", methods=['GET', 'POST'])_91def enqueue_call():_91 digit_pressed = request.args.get('Digits')_91 if digit_pressed == 1 :_91 language = "es"_91 else:_91 language = "en"_91_91 resp = VoiceResponse()_91 with resp.enqueue(None, workflowSid=workflow_sid) as e:_91 e.task('{"selected_language":"' + language + '"}')_91_91 return str(resp)_91_91@app.route("/agents", methods=['GET'])_91def generate_view():_91 worker_sid = request.args.get('WorkerSid')_91_91 worker_capability = WorkerCapabilityToken(account_sid, auth_token, workspace_sid, worker_sid)_91 worker_capability.allow_update_activities()_91 worker_capability.allow_update_reservations()_91_91 worker_token = worker_capability.to_jwt().decode('utf-8')_91_91 return render_template('agent.html', worker_token=worker_token)_91_91if __name__ == "__main__":_91 app.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:
_141<!DOCTYPE html>_141<html>_141<head>_141 <title>Customer Care - Voice Agent Screen</title>_141 <link rel="stylesheet" href="//media.twiliocdn.com/taskrouter/quickstart/agent.css"/>_141 <script src="https://sdk.twilio.com/js/taskrouter/v1.21/taskrouter.min.js" integrity="sha384-5fq+0qjayReAreRyHy38VpD3Gr9R2OYIzonwIkoGI4M9dhfKW6RWeRnZjfwSrpN8" crossorigin="anonymous"></script>_141 <script type="text/javascript">_141 /* Subscribe to a subset of the available TaskRouter.js events for a worker */_141 function registerTaskRouterCallbacks() {_141 worker.on('ready', function(worker) {_141 agentActivityChanged(worker.activityName);_141 logger("Successfully registered as: " + worker.friendlyName)_141 logger("Current activity is: " + worker.activityName);_141 });_141_141 worker.on('activity.update', function(worker) {_141 agentActivityChanged(worker.activityName);_141 logger("Worker activity changed to: " + worker.activityName);_141 });_141_141 worker.on("reservation.created", function(reservation) {_141 logger("-----");_141 logger("You have been reserved to handle a call!");_141 logger("Call from: " + reservation.task.attributes.from);_141 logger("Selected language: " + reservation.task.attributes.selected_language);_141 logger("-----");_141 });_141_141 worker.on("reservation.accepted", function(reservation) {_141 logger("Reservation " + reservation.sid + " accepted!");_141 });_141_141 worker.on("reservation.rejected", function(reservation) {_141 logger("Reservation " + reservation.sid + " rejected!");_141 });_141_141 worker.on("reservation.timeout", function(reservation) {_141 logger("Reservation " + reservation.sid + " timed out!");_141 });_141_141 worker.on("reservation.canceled", function(reservation) {_141 logger("Reservation " + reservation.sid + " canceled!");_141 });_141 }_141_141 /* Hook up the agent Activity buttons to TaskRouter.js */_141_141 function bindAgentActivityButtons() {_141 // Fetch the full list of available Activities from TaskRouter. Store each_141 // ActivitySid against the matching Friendly Name_141 var activitySids = {};_141 worker.activities.fetch(function(error, activityList) {_141 var activities = activityList.data;_141 var i = activities.length;_141 while (i--) {_141 activitySids[activities[i].friendlyName] = activities[i].sid;_141 }_141 });_141_141 /* For each button of class 'change-activity' in our Agent UI, look up the_141 ActivitySid corresponding to the Friendly Name in the button's next-activity_141 data attribute. Use Worker.js to transition the agent to that ActivitySid_141 when the button is clicked.*/_141 var elements = document.getElementsByClassName('change-activity');_141 var i = elements.length;_141 while (i--) {_141 elements[i].onclick = function() {_141 var nextActivity = this.dataset.nextActivity;_141 var nextActivitySid = activitySids[nextActivity];_141 worker.update({"ActivitySid":nextActivitySid});_141 }_141 }_141 }_141_141 /* Update the UI to reflect a change in Activity */_141_141 function agentActivityChanged(activity) {_141 hideAgentActivities();_141 showAgentActivity(activity);_141 }_141_141 function hideAgentActivities() {_141 var elements = document.getElementsByClassName('agent-activity');_141 var i = elements.length;_141 while (i--) {_141 elements[i].style.display = 'none';_141 }_141 }_141_141 function showAgentActivity(activity) {_141 activity = activity.toLowerCase();_141 var elements = document.getElementsByClassName(('agent-activity ' + activity));_141 elements.item(0).style.display = 'block';_141 }_141_141 /* Other stuff */_141_141 function logger(message) {_141 var log = document.getElementById('log');_141 log.value += "\n> " + message;_141 log.scrollTop = log.scrollHeight;_141 }_141_141 window.onload = function() {_141 // Initialize TaskRouter.js on page load using window.workerToken -_141 // a Twilio Capability token that was set from rendering the template with agents endpoint_141 logger("Initializing...");_141 window.worker = new Twilio.TaskRouter.Worker("{{ worker_token }}");_141_141 registerTaskRouterCallbacks();_141 bindAgentActivityButtons();_141 };_141 </script>_141</head>_141<body>_141<div class="content">_141 <section class="agent-activity offline">_141 <p class="activity">Offline</p>_141 <button class="change-activity" data-next-activity="Idle">Go Available</button>_141 </section>_141 <section class="agent-activity idle">_141 <p class="activity"><span>Available</span></p>_141 <button class="change-activity" data-next-activity="Offline">Go Offline</button>_141 </section>_141 <section class="agent-activity reserved">_141 <p class="activity">Reserved</p>_141 </section>_141 <section class="agent-activity busy">_141 <p class="activity">Busy</p>_141 </section>_141 <section class="agent-activity wrapup">_141 <p class="activity">Wrap-Up</p>_141 <button class="change-activity" data-next-activity="Idle">Go Available</button>_141 <button class="change-activity" data-next-activity="Offline">Go Offline</button>_141 </section>_141 <section class="log">_141 <textarea id="log" readonly="true"></textarea>_141 </section>_141</div>_141</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.