// Screen 6: CC Assistant, Genesys-flavoured contact centre console // Live-updating agent assist: profile, journey, sentiment, dynamic recommendations // Consultant speaks → AI listens → recommendations evolve → consultant approves → CRM summary const CCA_TX = [ { who: 'system', ts: '00:00', text: 'Handoff received from Hala · full context loaded' }, { who: 'aria', ts: '00:01', text: "Hi Amélie, Sarah's been on with me. EK202 cancelled, she chose EK203 direct, but wants to confirm with a human before I make the change. She's verified. I've prepared the action plan on your right." }, { who: 'consultant', ts: '00:08', text: "Hi Sarah, this is Amélie from Emirates. I have the full context from your conversation with Hala. Just to make sure I have everything, you'd like EK203 tonight, direct, seat 23A, vegetarian meal carried over, with priority disruption handling enabled. Is that all correct?" }, { who: 'sarah', ts: '00:21', text: "Yes, exactly that.", sent: 'pos' }, { who: 'consultant', ts: '00:24', text: "Perfect. Quick check, anything else worrying you about the new flight before I confirm it?" }, { who: 'sarah', ts: '00:30', text: "Just one thing, I want to make sure my Skywards Miles still credit for this leg.", sent: 'neutral' }, { who: 'consultant', ts: '00:36', text: "Good question. Let me check that for you." }, ]; // Wrap-up dialogue after Miles answer + approval const CCA_WRAPUP = [ { who: 'consultant', ts: '00:48', text: "Yes, confirmed. You'll earn the same Miles on EK203 as you would have on EK202. Distance is identical." }, { who: 'sarah', ts: '00:54', text: "Great. Then please go ahead." }, ]; const CCA_GOODBYE = [ { who: 'consultant', ts: '01:14', text: "All done, Sarah. EK203 is confirmed, seat 23A is yours, meal preference and priority handling are on. Confirmation just landed in your WhatsApp and email." }, { who: 'sarah', ts: '01:24', text: "Thank you so much, Amélie. That was honestly the easiest rebooking I've ever had." }, { who: 'consultant', ts: '01:30', text: "I'm so glad to hear that. Have a wonderful trip and a great meeting tomorrow. Safe travels with Emirates." }, { who: 'sarah', ts: '01:38', text: "Thanks, you too. Bye!" }, { who: 'consultant', ts: '01:40', text: "Goodbye, Sarah." }, { who: 'system', ts: '01:42', text: 'Call ended · 1m 42s · Wrapping up CRM' }, ]; const ACTIONS = [ { id: 'rebook', head: 'Rebook PNR MTCHL7 → EK203', meta: 'booking.update · 1 write' }, { id: 'seat', head: 'Reserve seat 23A on EK203', meta: 'seat.assign · 1 write' }, { id: 'meal', head: 'Transfer AVML meal preference', meta: 'profile.preference · 1 write' }, { id: 'priority',head: 'Enable priority disruption handling', meta: 'flag.set · 1 write' }, { id: 'notify', head: 'Send confirmation · WhatsApp + email', meta: 'notify.send · 2 writes' }, ]; // Next Best Action evolves with conversation turns const NBA_BY_STEP = [ { step: 0, focus: 'Greet & confirm context', hint: 'Customer escalated mid-conversation. Acknowledge handoff, do not re-verify.' }, { step: 2, focus: 'Read back the plan', hint: 'Sarah expects you to know everything. Recap EK203 + seat + meal + priority.' }, { step: 4, focus: 'Surface remaining concerns', hint: 'Open the floor before approving. She paused before agreeing earlier.' }, { step: 6, focus: 'Answer Miles credit question',hint: 'Policy DOC-2024-IRR-04 confirms identical Miles on Emirates-initiated rebookings.' }, { step: 8, focus: 'Approve & execute 5 actions', hint: 'All blockers cleared. Customer is ready. Execute the prepared plan.' }, { step: 9, focus: 'Wrap-up & warm goodbye', hint: 'Confirmation sent. Close with appreciation, set tone for the trip.' }, ]; function CopilotScreen({ onAdvance, autoAdvance }) { const [step, setStep] = React.useState(0); // transcript progression const [confirming, setConfirming] = React.useState(false); const [confirmed, setConfirmed] = React.useState(false); const [done, setDone] = React.useState({}); // executed action ids const [showSummary, setShowSummary] = React.useState(false); const [milesAnswered, setMilesAnswered] = React.useState(false); const [goodbyeStep, setGoodbyeStep] = React.useState(0); const [scale, setScale] = React.useState(1); const txRef = React.useRef(null); // scale to viewport React.useEffect(() => { const recalc = () => { const sx = (window.innerWidth - 40) / 1480; const sy = (window.innerHeight - 40) / 940; setScale(Math.min(sx, sy, 1)); }; recalc(); window.addEventListener('resize', recalc); return () => window.removeEventListener('resize', recalc); }, []); // auto-progress transcript at human speech speed React.useEffect(() => { if (step >= CCA_TX.length) return; const delays = [1800, 4400, 5200, 2600, 3200, 3800, 2800]; const t = setTimeout(() => setStep(s => s + 1), delays[step] || 3000); return () => clearTimeout(t); }, [step]); React.useEffect(() => { if (step === CCA_TX.length) { const t = setTimeout(() => setMilesAnswered(true), 2600); return () => clearTimeout(t); } }, [step]); // After confirmation, run goodbye sequence one line at a time React.useEffect(() => { if (!confirmed) return; if (goodbyeStep >= CCA_GOODBYE.length) return; const delays = [3200, 3600, 3400, 2400, 2000, 1800]; const t = setTimeout(() => setGoodbyeStep(s => s + 1), delays[goodbyeStep] || 2800); return () => clearTimeout(t); }, [confirmed, goodbyeStep]); React.useEffect(() => { if (txRef.current) txRef.current.scrollTop = txRef.current.scrollHeight; }, [step, milesAnswered, goodbyeStep, confirmed]); const handleConfirm = () => { if (confirming || confirmed) return; setConfirming(true); ACTIONS.forEach((a, i) => { setTimeout(() => setDone(d => ({ ...d, [a.id]: true })), 500 + i * 500); }); setTimeout(() => { setConfirming(false); setConfirmed(true); // Show summary after the goodbye sequence finishes setTimeout(() => setShowSummary(true), 18000); if (autoAdvance) setTimeout(() => onAdvance && onAdvance(), 26000); }, 500 + ACTIONS.length * 500 + 400); }; const visibleTx = CCA_TX.slice(0, step); // Progress index for NBA evolution const progressIdx = confirmed && goodbyeStep >= CCA_GOODBYE.length - 1 ? 9 : confirmed ? 8 : milesAnswered ? 6 : step >= 5 ? 4 : step >= 3 ? 2 : 0; const currentNBA = NBA_BY_STEP.slice().reverse().find(n => progressIdx >= n.step) || NBA_BY_STEP[0]; return (
{/* Top toolbar */}
CX
Cloud Contact Centre · CC Assistant on Genesys
My Workspace
Performance
Knowledge
Admin
On Call
Amélie Laurent
Amélie Laurent
Senior Consultant · Customer Care
{/* Interaction strip */}
Sarah Mitchell · Voice
Transferred from Hala · 00:42 ago
Imran Ali · Chat
On hold
REC 00:42
{/* Body grid */}
{/* LEFT, Profile + Sentiment + Journey */}
Customer 360
Sarah
Sarah Mitchell
◆ Skywards Silver · since 2019
Customer of 6 yrs · 23 trips
Booking
MTCHL7
Phone
+971 50 ••• 4127
Email
sarah.mitchell@gmail.com
Lifetime value
AED 47,200
Languages
EN · FR
Segments
Frequent NYC Mobile-first Prefers chat Time-sensitive trip
{/* Redesigned sentiment card */}
Sentiment · live
tracking
{(() => { const sentPct = confirmed ? 92 : milesAnswered ? 72 : step >= 4 ? 56 : step >= 2 ? 42 : 28; const sentLbl = confirmed ? 'Delighted' : milesAnswered ? 'Reassured' : step >= 4 ? 'Engaged' : step >= 2 ? 'Cautious' : 'Concerned'; const sentCls = confirmed ? 'pos' : milesAnswered ? 'imp' : step >= 4 ? 'imp' : step >= 2 ? 'cau' : 'con'; const sentTrend = confirmed ? '+64' : milesAnswered ? '+44' : step >= 4 ? '+28' : step >= 2 ? '+14' : '0'; const sentColor = confirmed ? '#4ADE80' : milesAnswered ? '#60A5FA' : step >= 4 ? '#93C5FD' : step >= 2 ? '#FCD34D' : '#FCA5A5'; // Flat SVG face icon, mood determined by mouth path + brow const SentFace = () => { // 5 moods: 0 worried, 1 cautious, 2 neutral, 3 reassured, 4 delighted const mood = confirmed ? 4 : milesAnswered ? 3 : step >= 4 ? 2 : step >= 2 ? 1 : 0; const mouthPaths = [ 'M16 30 Q24 24 32 30', // worried (frown) 'M16 28 Q24 26 32 28', // cautious (slight frown/flat) 'M16 28 L32 28', // neutral (line) 'M16 27 Q24 31 32 27', // reassured (small smile) 'M14 26 Q24 34 34 26', // delighted (big smile) ]; const eyeY = mood >= 3 ? 18 : 19; const browY1 = mood === 0 ? 14 : mood === 1 ? 15 : 13; const browY2 = mood === 0 ? 12 : mood === 1 ? 13 : 13; return ( {/* eyes */} {/* brows */} {mood <= 1 && ( <> )} {/* mouth */} ); }; const reasonText = confirmed ? 'All blockers cleared. Customer thanked you by name. Strong positive close.' : milesAnswered ? 'Skywards Miles question answered. Tone is opening up, ready to confirm.' : step >= 4 ? 'You acknowledged her plan without making her repeat it. Trust is building.' : step >= 2 ? 'Disruption + Manhattan deadline + channel switch. She is testing whether you know.' : 'Just escalated from voice with Hala. Time pressure makes this fragile.'; const drivers = confirmed ? [{ k: 'Confidence in plan', v: 'high' }, { k: 'Personal connection', v: 'high' }, { k: 'Time pressure', v: 'low' }] : milesAnswered ? [{ k: 'Confidence in plan', v: 'high' }, { k: 'Open concerns', v: 'none' }, { k: 'Time pressure', v: 'mid' }] : step >= 4 ? [{ k: 'Context awareness', v: 'high' }, { k: 'Open concerns', v: '1' }, { k: 'Time pressure', v: 'mid' }] : [{ k: 'Open concerns', v: '2+' }, { k: 'Channel switches', v: '3' }, { k: 'Time pressure', v: 'high' }]; return <>
{sentLbl}
{sentPct} /100 {sentTrend !== '0' && '↑'} {sentTrend}
{reasonText}
{drivers.map((d, i) => (
{d.k} {d.v}
))}
; })()}
Conversation Journey
Email opened 09:14
Cancellation notice · EK202
WhatsApp 09:16–20
12 messages with Hala · 2 options reviewed
Voice with Hala 09:21–24
Verified · clarified meal/lounge/risk
Voice with Amélie 09:24–now
{confirmed ? 'Confirmation sent · wrap-up' : 'Confirming rebook'}
5
Resolution & follow-up
Confirmation sent · trip monitored
{/* CENTER, Live transcript */}
Live Transcript LIVE · ASR
{visibleTx.map((line, i) => { if (line.who === 'system') { return
SYSTEM{line.ts}
{line.text}
; } const cls = line.who; const label = line.who === 'aria' ? 'HALA · AI' : line.who === 'consultant' ? 'AMÉLIE' : 'SARAH · PAX'; const isLast = i === visibleTx.length - 1; return (
{label} {line.ts}
{line.text}
); })} {milesAnswered && CCA_WRAPUP.map((line, i) => (
{line.who === 'consultant' ? 'AMÉLIE' : 'SARAH · PAX'} {line.ts}
{line.text}
))} {confirmed && (
SYSTEM01:08
✓ Booking updated by Amélie Laurent · EK203 confirmed · Confirmation dispatched
)} {confirmed && CCA_GOODBYE.slice(0, goodbyeStep).map((line, i) => { if (line.who === 'system') { return
SYSTEM{line.ts}
{line.text}
; } return (
{line.who === 'consultant' ? 'AMÉLIE' : 'SARAH · PAX'} {line.ts}
{line.text}
); })} {/* AI listening indicator */} {!confirmed && (
AI Assist is listening, recommendation panel updates as the call continues.
)}
{/* RIGHT, AI Recommendations */}
AI Assist · Next Best Action READY
{/* Live NBA hint card, evolves with conversation */}
{Math.min(progressIdx + 1, 9)} {currentNBA.focus}
{currentNBA.hint}
Recommended
Rebook to EK203 · Direct
DXB 22:40 → JFK 04:55 · 28→29 Apr
{/* Approve button at top */}
{/* Scrollable detail area */}
No fare diff Same cabin 12 seats Seat 23A held
Why this option
  • Customer chose explicitly during voice call (00:21)
  • Lands ~6h before her 11:00 NYC meeting
  • No price difference, no commercial decision needed
  • Schedule reliability: 28 of last 30 days on time
  • Skywards Miles credit confirmed identical (verified 00:48)
Actions on approval
{ACTIONS.map((a, i) => (
{done[a.id] ? : i + 1}
{a.head}
{a.meta}
))}
{/* Secondary card, knowledge */}
Knowledge · auto-fetched
Skywards Miles on rebooked flights
Miles credit equal to original flight when rebooking is initiated by Emirates due to operational disruption. Customer keeps tier benefits.
Source: Policy DOC-2024-IRR-04
Next likely question
"Will I be notified if EK203 also has issues?"
Suggested answer: Yes, priority disruption flag is being set. App push, SMS and email notifications enabled.
{/* Footer */}
Audit log: 14 events
All actions within policy scope
PII auto-redacted
cc-assistant.emirates.com
v 4.12.0
{/* End-of-call CRM summary */} {showSummary && onAdvance && onAdvance()} />}
); } function CCASummary({ onClose }) { return (
Interaction wrapped · CRM auto-populated
CASE-2026-0428-1147 · 1m 42s handle time · Closed by Amélie Laurent
{/* LEFT */}
Case Narrative AI-DRAFTED
Customer's flight EK202 (DXB→JFK, 28 Apr) was cancelled due to operational disruption. Customer had a time-critical meeting in NYC on 29 Apr at 11:00. Conversation began on email at 09:14, moved to WhatsApp at 09:16 with Hala (AI), where Hala presented two alternatives. Customer requested human verification and was transferred to Amélie Laurent at 09:24. Amélie confirmed Sarah's preferred option, EK203 (DXB 22:40 → JFK 04:55, direct), addressed her one remaining concern about Skywards Miles credit, and approved the prepared 5-action plan. All actions executed successfully. Customer expressed strong appreciation at close. No fare difference applied.
Resolution Actions
  • Rebooked PNR MTCHL7 from EK202 → EK203
  • Reserved seat 23A on EK203
  • Transferred AVML (vegetarian) meal preference
  • Enabled priority disruption handling flag on profile
  • Sent confirmation via WhatsApp + email
Customer Quotes (highlights)
"I want to make sure my Skywards Miles still credit for this leg."
"Then please go ahead." (approval)
"That was honestly the easiest rebooking I've ever had." (close)
{/* RIGHT */}
Case Fields
Case ID
CASE-26-0428-1147
PNR
MTCHL7
Customer
Sarah Mitchell
Tier
Skywards Silver
Reason
IRR · Cancel
Resolution
Rebook · same cabin
Channel path
Email → WA → Voice
Handled by
Hala (AI) → Amélie L.
AI handle time
07:24
Human handle time
01:42
Total elapsed
10:54
Final sentiment
92 / Delighted
Auto-Tags AI-CLASSIFIED
irr-rebook tier-silver multi-channel ai-to-human-handoff no-fare-diff first-touch-resolved policy-compliant positive-close
Follow-ups Scheduled
  • T-2h before EK203: app push status check
  • Post-arrival JFK: CSAT survey via email
  • Continuous: trip monitor active, alert on any schedule change
Synced to Genesys CCaaS · Skywards profile · Reservations system
); } function ClosingScreen() { const [scale, setScale] = React.useState(1); React.useEffect(() => { const recalc = () => { const sx = (window.innerWidth - 40) / 1440; const sy = (window.innerHeight - 40) / 900; setScale(Math.min(sx, sy, 1)); }; recalc(); window.addEventListener('resize', recalc); return () => window.removeEventListener('resize', recalc); }, []); return (
Closing summary

What just happened, and what it means

The Customer Story

Today (pre-AI)
  • Auto-rebook email arrives, lands her on a poor option
  • Calls contact centre, IVR menu, ~22 min wait
  • Re-explains booking, name, problem from scratch
  • Hears policy, hears a few options, decides under pressure
  • Booking changed, no real confirmation of preferences
~30 minutes · high friction
With Hala
  • Email cancellation, but a clear next step on WhatsApp
  • Hala greets her with full context, two curated options · 4 min
  • One-tap escalation to voice with Hala, no IVR, no wait · 3 min
  • Warm transfer to Amélie, who already has the plan ready
  • Approved in 1m 42s, confirmation lands instantly
~10 minutes · never repeats herself

The Consultant's Day

Consultant today
  • Avg handle time: ~9.5 min per call
  • Re-explaining vs resolving: ~60% of every call
  • Daily call volume: ~40 calls, mostly information gathering
  • Most repetitive task: verifying the customer, reading their booking
Consultant with Hala
  • Handoff handle time: 1m 42s for this case
  • Time spent re-explaining: ~5%, full context handed over
  • Daily resolution capacity: 4–6× higher
  • Role shifts from triage handler to senior decision-maker

The Data You Now Have

  • Structured conversation records
    Every channel, every turn, fully searchable
  • AI recommendations + acceptance rates
    Continuous improvement of options offered
  • Sentiment over time per customer
    Predict friction before it escalates
  • Tool call logs + decision paths
    Audit any action in two clicks
  • Channel preferences per segment
    Personalise outreach per passenger
  • Aggregate operational signals
    See where to invest in the experience next

One agent. Three channels. One passenger. One booking.
And your human consultant, always, irreversibly, in control of every change.

); } window.CopilotScreen = CopilotScreen; window.ClosingScreen = ClosingScreen;