// 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
My Workspace
Performance
Knowledge
Admin
On Call
Amélie Laurent
Senior Consultant · Customer Care
{/* Interaction strip */}
Sarah Mitchell · Voice
Transferred from Hala · 00:42 ago
REC
00:42
{/* Body grid */}
{/* LEFT, Profile + Sentiment + Journey */}
Customer 360
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 && (
SYSTEM 01: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 */}
{confirming
? <> Executing 5 actions…>
: confirmed
? <> Confirmed by Amélie>
: <> Approve & execute all 5>}
Modify
Reject
Escalate
{/* 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) => (
))}
{/* 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
Edit narrative
Save & next
Wrap up call
);
}
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;