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 server.rb
file to add an endpoint to generate a page based on a template.
1require 'rubygems'2require 'twilio-ruby'3require 'sinatra'4require 'json'56set :port, 808078# Get your Account Sid and Auth Token from twilio.com/user/account9account_sid = '{{ account_sid }}'10auth_token = '{{ auth_token }}'11workspace_sid = '{{ workspace_sid }}'12workflow_sid = '{{ workflow_sid }}'1314@client = Twilio::REST::Client.new(account_sid, auth_token)1516post '/assignment_callback' do17# Respond to assignment callbacks with accept instruction18content_type :json19# from must be a verified phone number from your twilio account20{21"instruction" => "dequeue",22"from" => "+15556667777",23"post_work_activity_sid" => "WA0123401234..."24}.to_json25end2627get '/create-task' do28# Create a task29task = @client.taskrouter.workspaces(workspace_sid)30.tasks31.create(32attributes: {33'selected_language' => 'es'34}.to_json,35workflow_sid: workflow_sid36)37task.attributes38end3940get '/accept_reservation' do41# Accept a Reservation42task_sid = params[:task_sid]43reservation_sid = params[:reservation_sid]4445reservation = @client.taskrouter.workspaces(workspace_sid)46.tasks(task_sid)47.reservations(reservation_sid)48.update(reservation_status: 'accepted')49reservation.worker_name50end5152get '/incoming_call' do53Twilio::TwiML::VoiceResponse.new do |r|54r.gather(action: '/enqueue_call', method: 'POST', timeout: 5, num_digits: 1) do |gather|55gather.say('Para Español oprime el uno.', language: 'es')56gather.say('For English, please hold or press two.', language: 'en')57end58end.to_s59end6061post '/enqueue_call' do62digit_pressed = params[:Digits]63if digit_pressed == 164language = "es"65else66language = "en"67end6869attributes = '{"selected_language":"'+language+'"}'7071Twilio::TwiML::Response.new do |r|72r.Enqueue workflowSid: workflow_sid do |e|73e.Task attributes74end75end.text76end7778get '/agents' do79worker_sid = params['WorkerSid']8081capability = Twilio::JWT::TaskRouterCapability.new(82account_sid, auth_token,83workspace_sid, worker_sid84)8586allow_activity_updates = Twilio::JWT::TaskRouterCapability::Policy.new(87Twilio::JWT::TaskRouterCapability::TaskRouterUtils88.all_activities(workspace_sid), 'POST', true89)90capability.add_policy(allow_activity_updates)9192allow_reservation_updates = Twilio::JWT::TaskRouterCapability::Policy.new(93Twilio::JWT::TaskRouterCapability::TaskRouterUtils94.all_reservations(workspace_sid, worker_sid), 'POST', true95)96capability.add_policy(allow_reservation_updates)9798worker_token = capability.to_s99100erb :agent, :locals => {:worker_token => worker_token}101end
Now create a folder called views. Inside that folder, create an ERB 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.