mirror of
https://dev.azure.com/globalhealthx/EMR/_git/helix-engage
synced 2026-04-11 10:23:27 +00:00
- Merged AI Assistant + Lead 360 tabs into single context-aware panel - Context section shows: lead profile, AI insight (live from event bus), appointments, activity - "On call with" banner only shows during active calls (isInCall prop) - AI Chat always available at bottom of panel - Phase 1 only — AI Chat panel needs full redesign with Vercel AI SDK tool calling (Phase 2) Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
887 lines
38 KiB
HTML
887 lines
38 KiB
HTML
<!DOCTYPE html>
|
||
<html lang="en">
|
||
<head>
|
||
<meta charset="UTF-8">
|
||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||
<title>Helix Engage — Weekly Update (Mar 18–25, 2026)</title>
|
||
<link rel="preconnect" href="https://fonts.googleapis.com">
|
||
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;700&family=Space+Grotesk:wght@400;500;600;700&display=swap" rel="stylesheet">
|
||
<style>
|
||
/* ===========================================
|
||
CSS CUSTOM PROPERTIES (DARK EXECUTIVE THEME)
|
||
=========================================== */
|
||
:root {
|
||
--bg-primary: #0b0e17;
|
||
--bg-secondary: #111827;
|
||
--bg-card: rgba(255,255,255,0.04);
|
||
--bg-card-hover: rgba(255,255,255,0.07);
|
||
--text-primary: #f0f2f5;
|
||
--text-secondary: #8892a4;
|
||
--text-muted: #4b5563;
|
||
--accent-cyan: #22d3ee;
|
||
--accent-violet: #a78bfa;
|
||
--accent-emerald: #34d399;
|
||
--accent-amber: #fbbf24;
|
||
--accent-rose: #fb7185;
|
||
--accent-blue: #60a5fa;
|
||
--glow-cyan: rgba(34,211,238,0.15);
|
||
--glow-violet: rgba(167,139,250,0.15);
|
||
--glow-emerald: rgba(52,211,153,0.15);
|
||
--font-display: 'Space Grotesk', sans-serif;
|
||
--font-body: 'DM Sans', sans-serif;
|
||
--slide-padding: clamp(2rem, 6vw, 5rem);
|
||
--ease-out-expo: cubic-bezier(0.16, 1, 0.3, 1);
|
||
--duration: 0.7s;
|
||
}
|
||
|
||
/* ===========================================
|
||
BASE RESET
|
||
=========================================== */
|
||
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
||
html { scroll-behavior: smooth; scroll-snap-type: y mandatory; }
|
||
body {
|
||
font-family: var(--font-body);
|
||
background: var(--bg-primary);
|
||
color: var(--text-primary);
|
||
overflow-x: hidden;
|
||
-webkit-font-smoothing: antialiased;
|
||
}
|
||
|
||
/* ===========================================
|
||
SLIDE CONTAINER
|
||
=========================================== */
|
||
.slide {
|
||
min-height: 100vh;
|
||
padding: var(--slide-padding);
|
||
scroll-snap-align: start;
|
||
display: flex;
|
||
flex-direction: column;
|
||
justify-content: center;
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
|
||
/* ===========================================
|
||
PROGRESS BAR
|
||
=========================================== */
|
||
.progress-bar {
|
||
position: fixed; top: 0; left: 0;
|
||
height: 3px; width: 0%;
|
||
background: linear-gradient(90deg, var(--accent-cyan), var(--accent-violet));
|
||
z-index: 100;
|
||
transition: width 0.3s ease;
|
||
}
|
||
|
||
/* ===========================================
|
||
NAVIGATION DOTS
|
||
=========================================== */
|
||
.nav-dots {
|
||
position: fixed; right: 1.5rem; top: 50%;
|
||
transform: translateY(-50%);
|
||
display: flex; flex-direction: column; gap: 10px;
|
||
z-index: 100;
|
||
}
|
||
.nav-dot {
|
||
width: 10px; height: 10px;
|
||
border-radius: 50%;
|
||
background: var(--text-muted);
|
||
border: none; cursor: pointer;
|
||
transition: all 0.3s ease;
|
||
}
|
||
.nav-dot.active {
|
||
background: var(--accent-cyan);
|
||
box-shadow: 0 0 12px var(--glow-cyan);
|
||
transform: scale(1.3);
|
||
}
|
||
|
||
/* ===========================================
|
||
SLIDE COUNTER
|
||
=========================================== */
|
||
.slide-counter {
|
||
position: fixed; bottom: 1.5rem; right: 2rem;
|
||
font-family: var(--font-display);
|
||
font-size: 0.85rem;
|
||
color: var(--text-muted);
|
||
z-index: 100;
|
||
letter-spacing: 0.1em;
|
||
}
|
||
|
||
/* ===========================================
|
||
REVEAL ANIMATIONS
|
||
=========================================== */
|
||
.reveal {
|
||
opacity: 0;
|
||
transform: translateY(35px);
|
||
transition: opacity var(--duration) var(--ease-out-expo),
|
||
transform var(--duration) var(--ease-out-expo);
|
||
}
|
||
.slide.visible .reveal { opacity: 1; transform: translateY(0); }
|
||
.reveal:nth-child(1) { transition-delay: 0.08s; }
|
||
.reveal:nth-child(2) { transition-delay: 0.16s; }
|
||
.reveal:nth-child(3) { transition-delay: 0.24s; }
|
||
.reveal:nth-child(4) { transition-delay: 0.32s; }
|
||
.reveal:nth-child(5) { transition-delay: 0.40s; }
|
||
.reveal:nth-child(6) { transition-delay: 0.48s; }
|
||
.reveal:nth-child(7) { transition-delay: 0.56s; }
|
||
.reveal:nth-child(8) { transition-delay: 0.64s; }
|
||
|
||
@media (prefers-reduced-motion: reduce) {
|
||
.reveal { transition: opacity 0.3s ease; transform: none; }
|
||
}
|
||
|
||
/* ===========================================
|
||
TYPOGRAPHY
|
||
=========================================== */
|
||
h1 { font-family: var(--font-display); font-weight: 700; }
|
||
h2 { font-family: var(--font-display); font-weight: 600; font-size: clamp(1.6rem, 4vw, 2.5rem); margin-bottom: 0.5em; }
|
||
h3 { font-family: var(--font-display); font-weight: 500; font-size: 1.1rem; }
|
||
p, li { line-height: 1.65; }
|
||
|
||
.label {
|
||
display: inline-block;
|
||
font-family: var(--font-display);
|
||
text-transform: uppercase;
|
||
letter-spacing: 0.15em;
|
||
font-size: 0.7rem;
|
||
font-weight: 600;
|
||
padding: 0.3em 0.9em;
|
||
border-radius: 100px;
|
||
margin-bottom: 1rem;
|
||
}
|
||
|
||
/* ===========================================
|
||
TITLE SLIDE
|
||
=========================================== */
|
||
.title-slide {
|
||
text-align: center;
|
||
background:
|
||
radial-gradient(ellipse at 30% 70%, rgba(34,211,238,0.08) 0%, transparent 50%),
|
||
radial-gradient(ellipse at 70% 30%, rgba(167,139,250,0.08) 0%, transparent 50%),
|
||
var(--bg-primary);
|
||
}
|
||
.title-slide h1 {
|
||
font-size: clamp(2.5rem, 6vw, 4.5rem);
|
||
background: linear-gradient(135deg, var(--accent-cyan) 0%, var(--accent-violet) 50%, var(--accent-emerald) 100%);
|
||
-webkit-background-clip: text;
|
||
-webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
line-height: 1.15;
|
||
margin-bottom: 0.3em;
|
||
}
|
||
.title-slide .subtitle {
|
||
font-size: clamp(1rem, 2vw, 1.4rem);
|
||
color: var(--text-secondary);
|
||
margin-bottom: 0.4em;
|
||
}
|
||
.title-slide .date-range {
|
||
font-family: var(--font-display);
|
||
color: var(--text-muted);
|
||
font-size: 0.9rem;
|
||
letter-spacing: 0.08em;
|
||
}
|
||
|
||
/* ===========================================
|
||
STAT CARDS
|
||
=========================================== */
|
||
.stats-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
|
||
gap: 1.2rem;
|
||
margin-top: 1.5rem;
|
||
}
|
||
.stat-card {
|
||
background: var(--bg-card);
|
||
border: 1px solid rgba(255,255,255,0.06);
|
||
border-radius: 16px;
|
||
padding: 1.8rem 1.5rem;
|
||
text-align: center;
|
||
transition: all 0.4s var(--ease-out-expo);
|
||
position: relative;
|
||
overflow: hidden;
|
||
}
|
||
.stat-card::after {
|
||
content: '';
|
||
position: absolute; top: 0; left: 0; right: 0;
|
||
height: 2px;
|
||
background: linear-gradient(90deg, transparent, var(--accent-cyan), transparent);
|
||
opacity: 0;
|
||
transition: opacity 0.4s ease;
|
||
}
|
||
.stat-card:hover { background: var(--bg-card-hover); transform: translateY(-4px); }
|
||
.stat-card:hover::after { opacity: 1; }
|
||
.stat-number {
|
||
font-family: var(--font-display);
|
||
font-size: 3rem;
|
||
font-weight: 700;
|
||
line-height: 1;
|
||
margin-bottom: 0.3em;
|
||
}
|
||
.stat-number.cyan { color: var(--accent-cyan); }
|
||
.stat-number.violet { color: var(--accent-violet); }
|
||
.stat-number.emerald { color: var(--accent-emerald); }
|
||
.stat-number.amber { color: var(--accent-amber); }
|
||
.stat-label { color: var(--text-secondary); font-size: 0.85rem; }
|
||
|
||
/* ===========================================
|
||
CONTENT CARDS
|
||
=========================================== */
|
||
.card-grid {
|
||
display: grid;
|
||
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
|
||
gap: 1.2rem;
|
||
margin-top: 1.2rem;
|
||
}
|
||
.content-card {
|
||
background: var(--bg-card);
|
||
border: 1px solid rgba(255,255,255,0.06);
|
||
border-radius: 14px;
|
||
padding: 1.5rem;
|
||
transition: all 0.3s var(--ease-out-expo);
|
||
}
|
||
.content-card:hover { background: var(--bg-card-hover); border-color: rgba(255,255,255,0.1); }
|
||
.content-card h3 { margin-bottom: 0.6rem; }
|
||
.content-card ul {
|
||
list-style: none; padding: 0;
|
||
}
|
||
.content-card li {
|
||
position: relative;
|
||
padding-left: 1.2em;
|
||
margin-bottom: 0.45em;
|
||
color: var(--text-secondary);
|
||
font-size: 0.9rem;
|
||
}
|
||
.content-card li::before {
|
||
content: '›';
|
||
position: absolute;
|
||
left: 0;
|
||
color: var(--accent-cyan);
|
||
font-weight: 700;
|
||
}
|
||
|
||
/* ===========================================
|
||
TIMELINE
|
||
=========================================== */
|
||
.timeline {
|
||
position: relative;
|
||
padding-left: 2rem;
|
||
margin-top: 1.5rem;
|
||
}
|
||
.timeline::before {
|
||
content: '';
|
||
position: absolute; left: 0; top: 0; bottom: 0;
|
||
width: 2px;
|
||
background: linear-gradient(to bottom, var(--accent-cyan), var(--accent-violet), var(--accent-emerald));
|
||
opacity: 0.4;
|
||
}
|
||
.tl-item {
|
||
position: relative;
|
||
margin-bottom: 1.5rem;
|
||
padding-left: 1rem;
|
||
}
|
||
.tl-item::before {
|
||
content: '';
|
||
position: absolute; left: -2.35rem; top: 0.3em;
|
||
width: 10px; height: 10px;
|
||
border-radius: 50%;
|
||
background: var(--accent-cyan);
|
||
border: 2px solid var(--bg-primary);
|
||
}
|
||
.tl-date {
|
||
font-family: var(--font-display);
|
||
font-size: 0.75rem;
|
||
color: var(--accent-cyan);
|
||
letter-spacing: 0.08em;
|
||
margin-bottom: 0.15em;
|
||
}
|
||
.tl-title {
|
||
font-weight: 600;
|
||
font-size: 1rem;
|
||
margin-bottom: 0.15em;
|
||
}
|
||
.tl-desc {
|
||
font-size: 0.85rem;
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
/* ===========================================
|
||
REPO BADGE
|
||
=========================================== */
|
||
.repo-badge {
|
||
display: inline-block;
|
||
font-family: var(--font-display);
|
||
font-size: 0.65rem;
|
||
padding: 2px 8px;
|
||
border-radius: 4px;
|
||
font-weight: 600;
|
||
letter-spacing: 0.05em;
|
||
margin-left: 0.5em;
|
||
vertical-align: middle;
|
||
}
|
||
.badge-frontend { background: rgba(34,211,238,0.15); color: var(--accent-cyan); }
|
||
.badge-server { background: rgba(167,139,250,0.15); color: var(--accent-violet); }
|
||
.badge-sdk { background: rgba(52,211,153,0.15); color: var(--accent-emerald); }
|
||
|
||
/* ===========================================
|
||
PILL LIST
|
||
=========================================== */
|
||
.pill-list {
|
||
display: flex; flex-wrap: wrap; gap: 0.5rem;
|
||
margin-top: 0.8rem;
|
||
}
|
||
.pill {
|
||
display: inline-block;
|
||
font-size: 0.78rem;
|
||
padding: 0.3em 0.9em;
|
||
border-radius: 100px;
|
||
background: var(--bg-card);
|
||
border: 1px solid rgba(255,255,255,0.08);
|
||
color: var(--text-secondary);
|
||
}
|
||
|
||
/* ===========================================
|
||
SECTION HEADER
|
||
=========================================== */
|
||
.section-header {
|
||
display: flex;
|
||
align-items: center;
|
||
gap: 0.7rem;
|
||
margin-bottom: 0.3rem;
|
||
}
|
||
.section-icon {
|
||
width: 36px; height: 36px;
|
||
border-radius: 10px;
|
||
display: flex; align-items: center; justify-content: center;
|
||
font-size: 1.1rem;
|
||
}
|
||
|
||
/* ===========================================
|
||
KEYBOARD HINT
|
||
=========================================== */
|
||
.keyboard-hint {
|
||
position: fixed; bottom: 1.5rem; left: 2rem;
|
||
font-size: 0.75rem; color: var(--text-muted);
|
||
z-index: 100;
|
||
display: flex; align-items: center; gap: 0.5rem;
|
||
opacity: 0;
|
||
animation: hintFade 0.6s 2s forwards;
|
||
}
|
||
.key {
|
||
display: inline-block;
|
||
padding: 2px 8px;
|
||
border: 1px solid var(--text-muted);
|
||
border-radius: 4px;
|
||
font-family: var(--font-display);
|
||
font-size: 0.7rem;
|
||
}
|
||
@keyframes hintFade { to { opacity: 1; } }
|
||
|
||
/* ===========================================
|
||
CLOSING SLIDE
|
||
=========================================== */
|
||
.closing-slide {
|
||
text-align: center;
|
||
background:
|
||
radial-gradient(ellipse at 50% 50%, rgba(34,211,238,0.06) 0%, transparent 60%),
|
||
var(--bg-primary);
|
||
}
|
||
.closing-slide h2 {
|
||
font-size: clamp(1.8rem, 4vw, 3rem);
|
||
background: linear-gradient(135deg, var(--accent-emerald), var(--accent-cyan));
|
||
-webkit-background-clip: text; -webkit-text-fill-color: transparent;
|
||
background-clip: text;
|
||
}
|
||
|
||
/* ===========================================
|
||
RESPONSIVE
|
||
=========================================== */
|
||
@media (max-width: 768px) {
|
||
.nav-dots, .keyboard-hint { display: none; }
|
||
.stats-grid { grid-template-columns: repeat(2, 1fr); }
|
||
.card-grid { grid-template-columns: 1fr; }
|
||
}
|
||
</style>
|
||
</head>
|
||
<body>
|
||
<!-- Progress bar -->
|
||
<div class="progress-bar" id="progressBar"></div>
|
||
|
||
<!-- Navigation dots -->
|
||
<nav class="nav-dots" id="navDots"></nav>
|
||
|
||
<!-- Slide counter -->
|
||
<div class="slide-counter" id="slideCounter"></div>
|
||
|
||
<!-- Keyboard hint -->
|
||
<div class="keyboard-hint">
|
||
<span class="key">↑</span><span class="key">↓</span> or <span class="key">Space</span> to navigate
|
||
</div>
|
||
|
||
<!-- ======================================
|
||
SLIDE 1: TITLE
|
||
====================================== -->
|
||
<section class="slide title-slide">
|
||
<div class="reveal">
|
||
<span class="label" style="background: var(--glow-cyan); color: var(--accent-cyan);">Weekly Engineering Update</span>
|
||
</div>
|
||
<h1 class="reveal">Helix Engage</h1>
|
||
<p class="subtitle reveal">Contact Center CRM · Real-time Telephony · AI Copilot</p>
|
||
<p class="date-range reveal">March 18 – 25, 2026</p>
|
||
</section>
|
||
|
||
<!-- ======================================
|
||
SLIDE 2: AT A GLANCE
|
||
====================================== -->
|
||
<section class="slide">
|
||
<div class="reveal">
|
||
<span class="label" style="background: var(--glow-violet); color: var(--accent-violet);">At a Glance</span>
|
||
</div>
|
||
<h2 class="reveal">Week in Numbers</h2>
|
||
<div class="stats-grid">
|
||
<div class="stat-card reveal">
|
||
<div class="stat-number cyan" data-count="78">0</div>
|
||
<div class="stat-label">Total Commits</div>
|
||
</div>
|
||
<div class="stat-card reveal">
|
||
<div class="stat-number violet" data-count="3">0</div>
|
||
<div class="stat-label">Repositories</div>
|
||
</div>
|
||
<div class="stat-card reveal">
|
||
<div class="stat-number emerald" data-count="8">0</div>
|
||
<div class="stat-label">Days Active</div>
|
||
</div>
|
||
<div class="stat-card reveal">
|
||
<div class="stat-number amber" data-count="50">0</div>
|
||
<div class="stat-label">Frontend Commits</div>
|
||
</div>
|
||
</div>
|
||
<div class="pill-list reveal" style="margin-top:1.5rem; justify-content: center;">
|
||
<span class="pill" style="border-color: rgba(34,211,238,0.3); color: var(--accent-cyan);">helix-engage <b>50</b></span>
|
||
<span class="pill" style="border-color: rgba(167,139,250,0.3); color: var(--accent-violet);">helix-engage-server <b>27</b></span>
|
||
<span class="pill" style="border-color: rgba(52,211,153,0.3); color: var(--accent-emerald);">FortyTwoApps/SDK <b>1</b></span>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ======================================
|
||
SLIDE 3: TELEPHONY & SIP
|
||
====================================== -->
|
||
<section class="slide">
|
||
<div class="reveal">
|
||
<div class="section-header">
|
||
<div class="section-icon" style="background: var(--glow-cyan);">📞</div>
|
||
<span class="label" style="background: var(--glow-cyan); color: var(--accent-cyan);">Core Infrastructure</span>
|
||
</div>
|
||
</div>
|
||
<h2 class="reveal">Telephony & SIP Overhaul</h2>
|
||
<div class="card-grid">
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-cyan);">Outbound Calling <span class="repo-badge badge-frontend">Frontend</span></h3>
|
||
<ul>
|
||
<li>Direct SIP call from browser — no Kookoo bridge needed</li>
|
||
<li>Immediate call card UI with auto-answer SIP bridge</li>
|
||
<li>End Call label fix, force active state after auto-answer</li>
|
||
<li>Reset outboundPending on call end to prevent inbound poisoning</li>
|
||
</ul>
|
||
</div>
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-violet);">Ozonetel Integration <span class="repo-badge badge-server">Server</span></h3>
|
||
<ul>
|
||
<li>Ozonetel V3 dial endpoint + webhook handler for call events</li>
|
||
<li>Kookoo IVR outbound bridging (deprecated → direct SIP)</li>
|
||
<li>Set Disposition API for ACW release</li>
|
||
<li>Force Ready endpoint for agent state management</li>
|
||
<li>Token: 10-min cache, 401 invalidation, refresh on login</li>
|
||
</ul>
|
||
</div>
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-cyan);">SIP & Agent State <span class="repo-badge badge-frontend">Frontend</span></h3>
|
||
<ul>
|
||
<li>SIP driven by Agent entity with token refresh</li>
|
||
<li>Dynamic SIP from agentConfig, logout cleanup, heartbeat</li>
|
||
<li>Centralised outbound dial into <code>useSip().dialOutbound()</code></li>
|
||
<li>UCID tracking from SIP headers for Ozonetel disposition</li>
|
||
<li>Network indicator for connection health</li>
|
||
</ul>
|
||
</div>
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-violet);">Multi-Agent & Sessions <span class="repo-badge badge-server">Server</span></h3>
|
||
<ul>
|
||
<li>Multi-agent SIP with Redis session lockout</li>
|
||
<li>Strict duplicate login lockout — one device per agent</li>
|
||
<li>Session lock stores IP + timestamp for debugging</li>
|
||
<li>SSE agent state broadcast for real-time supervisor view</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ======================================
|
||
SLIDE 4: CALL DESK & UX
|
||
====================================== -->
|
||
<section class="slide">
|
||
<div class="reveal">
|
||
<div class="section-header">
|
||
<div class="section-icon" style="background: var(--glow-emerald);">🖥️</div>
|
||
<span class="label" style="background: var(--glow-emerald); color: var(--accent-emerald);">User Experience</span>
|
||
</div>
|
||
</div>
|
||
<h2 class="reveal">Call Desk & Agent UX</h2>
|
||
<div class="card-grid">
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-emerald);">Call Desk Redesign</h3>
|
||
<ul>
|
||
<li>2-panel layout with collapsible sidebar & inline AI</li>
|
||
<li>Collapsible context panel, worklist/calls tabs, phone numbers</li>
|
||
<li>Pinned header & chat input, numpad dialler</li>
|
||
<li>Ringtone support for incoming calls</li>
|
||
</ul>
|
||
</div>
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-emerald);">Post-Call Workflow</h3>
|
||
<ul>
|
||
<li>Disposition → appointment booking → follow-up creation</li>
|
||
<li>Disposition returns straight to worklist — no intermediate screens</li>
|
||
<li>Send disposition to sidecar with UCID for Ozonetel ACW</li>
|
||
<li>Enquiry in post-call, appointment skip button</li>
|
||
</ul>
|
||
</div>
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-emerald);">UI Polish</h3>
|
||
<ul>
|
||
<li>FontAwesome Pro Duotone icon migration (all icons)</li>
|
||
<li>Tooltips, sticky headers, roles, search, AI prompts</li>
|
||
<li>Fix React error #520 (isRowHeader) in production tables</li>
|
||
<li>AI scroll containment, brand tokens refresh</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ======================================
|
||
SLIDE 5: FEATURES SHIPPED
|
||
====================================== -->
|
||
<section class="slide">
|
||
<div class="reveal">
|
||
<div class="section-header">
|
||
<div class="section-icon" style="background: rgba(251,191,36,0.15);">🚀</div>
|
||
<span class="label" style="background: rgba(251,191,36,0.15); color: var(--accent-amber);">Features Shipped</span>
|
||
</div>
|
||
</div>
|
||
<h2 class="reveal">Major Features</h2>
|
||
<div class="card-grid">
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-amber);">Supervisor Module</h3>
|
||
<ul>
|
||
<li>Team performance analytics page</li>
|
||
<li>Live monitor with active calls visibility</li>
|
||
<li>Master data management pages</li>
|
||
<li>Server: team perf + active calls endpoints</li>
|
||
</ul>
|
||
</div>
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-amber);">Missed Call Queue (Phase 2)</h3>
|
||
<ul>
|
||
<li>Missed call queue ingestion & worklist</li>
|
||
<li>Auto-assignment engine for agents</li>
|
||
<li>Login redesign with role-based routing</li>
|
||
<li>Lead lookup for missed callers</li>
|
||
</ul>
|
||
</div>
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-amber);">Agent Features (Phase 1)</h3>
|
||
<ul>
|
||
<li>Agent status toggle (Ready / Not Ready / Break)</li>
|
||
<li>Global search across patients, leads, calls</li>
|
||
<li>Enquiry form for new patient intake</li>
|
||
<li>My Performance page + logout modal</li>
|
||
</ul>
|
||
</div>
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-amber);">Recording Analysis</h3>
|
||
<ul>
|
||
<li>Deepgram diarization + AI insights</li>
|
||
<li>Redis caching layer for analysis results</li>
|
||
<li>Full-stack: frontend player + server module</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ======================================
|
||
SLIDE 6: DATA & BACKEND
|
||
====================================== -->
|
||
<section class="slide">
|
||
<div class="reveal">
|
||
<div class="section-header">
|
||
<div class="section-icon" style="background: var(--glow-violet);">⚙️</div>
|
||
<span class="label" style="background: var(--glow-violet); color: var(--accent-violet);">Backend & Data</span>
|
||
</div>
|
||
</div>
|
||
<h2 class="reveal">Backend & Data Layer</h2>
|
||
<div class="card-grid">
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-violet);">Platform Data Wiring</h3>
|
||
<ul>
|
||
<li>Migrated frontend to Jotai + Vercel AI SDK</li>
|
||
<li>Corrected all 7 GraphQL queries (field names, LINKS/PHONES)</li>
|
||
<li>Webhook handler for Ozonetel call records</li>
|
||
<li>Complete seeder: 5 doctors, appointments linked, agent names match</li>
|
||
</ul>
|
||
</div>
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-violet);">Server Endpoints</h3>
|
||
<ul>
|
||
<li>Call control, recording, CDR, missed calls, live call assist</li>
|
||
<li>Agent summary, AHT, performance aggregation</li>
|
||
<li>Token refresh endpoint for auto-renewal</li>
|
||
<li>Search module with full-text capabilities</li>
|
||
</ul>
|
||
</div>
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-violet);">Data Pages Built</h3>
|
||
<ul>
|
||
<li>Worklist table, call history, patients, dashboard</li>
|
||
<li>Reports, team dashboard, campaigns, settings</li>
|
||
<li>Agent detail page, campaign edit slideout</li>
|
||
<li>Appointments page with data refresh on login</li>
|
||
</ul>
|
||
</div>
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-emerald);">SDK App <span class="repo-badge badge-sdk">FortyTwoApps</span></h3>
|
||
<ul>
|
||
<li>Helix Engage SDK app entity definitions</li>
|
||
<li>Call center CRM object model for Fortytwo platform</li>
|
||
<li>Foundation for platform-native data integration</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ======================================
|
||
SLIDE 7: DEPLOYMENT & OPS
|
||
====================================== -->
|
||
<section class="slide">
|
||
<div class="reveal">
|
||
<div class="section-header">
|
||
<div class="section-icon" style="background: rgba(251,113,133,0.15);">🛠️</div>
|
||
<span class="label" style="background: rgba(251,113,133,0.15); color: var(--accent-rose);">Operations</span>
|
||
</div>
|
||
</div>
|
||
<h2 class="reveal">Deployment & DevOps</h2>
|
||
<div class="card-grid">
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-rose);">Deployment</h3>
|
||
<ul>
|
||
<li>Deployed to Hostinger VPS with Docker</li>
|
||
<li>Switched to global_healthx Ozonetel account</li>
|
||
<li>Dockerfile for server-side containerization</li>
|
||
</ul>
|
||
</div>
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-rose);">AI & Testing</h3>
|
||
<ul>
|
||
<li>Migrated AI to Vercel AI SDK + OpenAI provider</li>
|
||
<li>AI flow test script — validates auth, lead, patient, doctor, appointments</li>
|
||
<li>Live call assist integration</li>
|
||
</ul>
|
||
</div>
|
||
<div class="content-card reveal">
|
||
<h3 style="color: var(--accent-rose);">Documentation</h3>
|
||
<ul>
|
||
<li>Team onboarding README with architecture guide</li>
|
||
<li>Supervisor module spec + implementation plan</li>
|
||
<li>Multi-agent spec + plan</li>
|
||
<li>Next session plans documented in commits</li>
|
||
</ul>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ======================================
|
||
SLIDE 8: TIMELINE
|
||
====================================== -->
|
||
<section class="slide">
|
||
<div class="reveal">
|
||
<div class="section-header">
|
||
<div class="section-icon" style="background: var(--glow-cyan);">📅</div>
|
||
<span class="label" style="background: var(--glow-cyan); color: var(--accent-cyan);">Day by Day</span>
|
||
</div>
|
||
</div>
|
||
<h2 class="reveal">Development Timeline</h2>
|
||
<div class="timeline reveal" style="max-height: 60vh; overflow-y: auto; padding-right: 1rem;">
|
||
<div class="tl-item">
|
||
<div class="tl-date">MAR 18 (Tue)</div>
|
||
<div class="tl-title">Foundation Day</div>
|
||
<div class="tl-desc">Call desk redesign, Jotai + Vercel AI SDK migration, seeder with 5 doctors + linked appointments, AI flow test script, deployed to VPS</div>
|
||
</div>
|
||
<div class="tl-item">
|
||
<div class="tl-date">MAR 19 (Wed)</div>
|
||
<div class="tl-title">Data Layer Sprint</div>
|
||
<div class="tl-desc">All data pages built (worklist, call history, patients, dashboard, reports), post-call workflow (disposition → booking), GraphQL fixes, Kookoo IVR outbound, outbound call UI</div>
|
||
</div>
|
||
<div class="tl-item">
|
||
<div class="tl-date">MAR 20 (Thu)</div>
|
||
<div class="tl-title">Telephony Breakthrough</div>
|
||
<div class="tl-desc">Direct SIP call from browser replacing Kookoo bridge, UCID tracking, Force Ready, Ozonetel Set Disposition, telephony overhaul</div>
|
||
</div>
|
||
<div class="tl-item">
|
||
<div class="tl-date">MAR 21 (Fri)</div>
|
||
<div class="tl-title">Agent Experience</div>
|
||
<div class="tl-desc">Phase 1 shipped — agent status toggle, global search, enquiry form, My Performance page, full FontAwesome icon migration, agent summary/AHT endpoints</div>
|
||
</div>
|
||
<div class="tl-item">
|
||
<div class="tl-date">MAR 23 (Sun)</div>
|
||
<div class="tl-title">Scale & Reliability</div>
|
||
<div class="tl-desc">Phase 2 — missed call queue + auto-assignment, multi-agent SIP with Redis lockout, duplicate login prevention, Patient 360 rewrite, onboarding docs, SDK entity defs</div>
|
||
</div>
|
||
<div class="tl-item">
|
||
<div class="tl-date">MAR 24 (Mon)</div>
|
||
<div class="tl-title">Supervisor Module</div>
|
||
<div class="tl-desc">Supervisor module with team performance + live monitor + master data, SSE agent state, UUID fix, maintenance module, QA bug sweep, supervisor endpoints</div>
|
||
</div>
|
||
<div class="tl-item">
|
||
<div class="tl-date">MAR 25 (Tue)</div>
|
||
<div class="tl-title">Intelligence Layer</div>
|
||
<div class="tl-desc">Call recording analysis with Deepgram diarization + AI insights, SIP driven by Agent entity, token refresh, network indicator</div>
|
||
</div>
|
||
</div>
|
||
</section>
|
||
|
||
<!-- ======================================
|
||
SLIDE 9: CLOSING
|
||
====================================== -->
|
||
<section class="slide closing-slide">
|
||
<h2 class="reveal">78 commits. 8 days. Ship mode. 🚢</h2>
|
||
<p class="reveal" style="color: var(--text-secondary); margin-top: 0.6em; font-size: 1.1rem; max-width: 600px; margin-inline: auto;">
|
||
From browser-native SIP calling to AI-powered recording analysis — Helix Engage is becoming a production contact center platform.
|
||
</p>
|
||
<div class="pill-list reveal" style="justify-content: center; margin-top: 1.5rem;">
|
||
<span class="pill" style="border-color: rgba(34,211,238,0.3); color: var(--accent-cyan);">SIP Calling ✓</span>
|
||
<span class="pill" style="border-color: rgba(167,139,250,0.3); color: var(--accent-violet);">Multi-Agent ✓</span>
|
||
<span class="pill" style="border-color: rgba(52,211,153,0.3); color: var(--accent-emerald);">Supervisor Module ✓</span>
|
||
<span class="pill" style="border-color: rgba(251,191,36,0.3); color: var(--accent-amber);">AI Copilot ✓</span>
|
||
<span class="pill" style="border-color: rgba(251,113,133,0.3); color: var(--accent-rose);">Recording Analysis ✓</span>
|
||
</div>
|
||
<p class="reveal" style="color: var(--text-muted); margin-top: 2rem; font-size: 0.8rem;">Satya Suman Sari · FortyTwo Platform</p>
|
||
</section>
|
||
|
||
<!-- ===========================================
|
||
SLIDE PRESENTATION CONTROLLER
|
||
=========================================== -->
|
||
<script>
|
||
class SlidePresentation {
|
||
constructor() {
|
||
this.slides = document.querySelectorAll('.slide');
|
||
this.progressBar = document.getElementById('progressBar');
|
||
this.navDots = document.getElementById('navDots');
|
||
this.slideCounter = document.getElementById('slideCounter');
|
||
this.currentSlide = 0;
|
||
|
||
this.createNavDots();
|
||
this.setupObserver();
|
||
this.setupKeyboard();
|
||
this.setupTouch();
|
||
this.animateCounters();
|
||
this.updateCounter();
|
||
}
|
||
|
||
/* --- Navigation dots --- */
|
||
createNavDots() {
|
||
this.slides.forEach((_, i) => {
|
||
const dot = document.createElement('button');
|
||
dot.classList.add('nav-dot');
|
||
dot.setAttribute('aria-label', `Go to slide ${i + 1}`);
|
||
dot.addEventListener('click', () => this.goToSlide(i));
|
||
this.navDots.appendChild(dot);
|
||
});
|
||
}
|
||
|
||
/* --- Intersection Observer for reveal animations --- */
|
||
setupObserver() {
|
||
const observer = new IntersectionObserver((entries) => {
|
||
entries.forEach(entry => {
|
||
if (entry.isIntersecting) {
|
||
entry.target.classList.add('visible');
|
||
const idx = Array.from(this.slides).indexOf(entry.target);
|
||
if (idx !== -1) {
|
||
this.currentSlide = idx;
|
||
this.updateProgress();
|
||
this.updateDots();
|
||
this.updateCounter();
|
||
if (idx === 1) this.animateCounters();
|
||
}
|
||
}
|
||
});
|
||
}, { threshold: 0.45 });
|
||
|
||
this.slides.forEach(slide => observer.observe(slide));
|
||
}
|
||
|
||
/* --- Keyboard navigation --- */
|
||
setupKeyboard() {
|
||
document.addEventListener('keydown', (e) => {
|
||
if (e.key === 'ArrowDown' || e.key === ' ' || e.key === 'ArrowRight') {
|
||
e.preventDefault();
|
||
this.goToSlide(Math.min(this.currentSlide + 1, this.slides.length - 1));
|
||
} else if (e.key === 'ArrowUp' || e.key === 'ArrowLeft') {
|
||
e.preventDefault();
|
||
this.goToSlide(Math.max(this.currentSlide - 1, 0));
|
||
}
|
||
});
|
||
}
|
||
|
||
/* --- Touch swipe support --- */
|
||
setupTouch() {
|
||
let startY = 0;
|
||
document.addEventListener('touchstart', (e) => { startY = e.touches[0].clientY; });
|
||
document.addEventListener('touchend', (e) => {
|
||
const dy = startY - e.changedTouches[0].clientY;
|
||
if (Math.abs(dy) > 50) {
|
||
if (dy > 0) this.goToSlide(Math.min(this.currentSlide + 1, this.slides.length - 1));
|
||
else this.goToSlide(Math.max(this.currentSlide - 1, 0));
|
||
}
|
||
});
|
||
}
|
||
|
||
goToSlide(idx) {
|
||
this.slides[idx].scrollIntoView({ behavior: 'smooth' });
|
||
}
|
||
|
||
updateProgress() {
|
||
const pct = ((this.currentSlide) / (this.slides.length - 1)) * 100;
|
||
this.progressBar.style.width = pct + '%';
|
||
}
|
||
|
||
updateDots() {
|
||
this.navDots.querySelectorAll('.nav-dot').forEach((dot, i) => {
|
||
dot.classList.toggle('active', i === this.currentSlide);
|
||
});
|
||
}
|
||
|
||
updateCounter() {
|
||
this.slideCounter.textContent = `${this.currentSlide + 1} / ${this.slides.length}`;
|
||
}
|
||
|
||
/* --- Animate counter numbers --- */
|
||
animateCounters() {
|
||
document.querySelectorAll('[data-count]').forEach(el => {
|
||
const target = parseInt(el.dataset.count);
|
||
const duration = 1200;
|
||
const start = performance.now();
|
||
const animate = (now) => {
|
||
const elapsed = now - start;
|
||
const progress = Math.min(elapsed / duration, 1);
|
||
const eased = 1 - Math.pow(1 - progress, 3);
|
||
el.textContent = Math.round(eased * target);
|
||
if (progress < 1) requestAnimationFrame(animate);
|
||
};
|
||
requestAnimationFrame(animate);
|
||
});
|
||
}
|
||
}
|
||
|
||
// Initialize
|
||
new SlidePresentation();
|
||
</script>
|
||
</body>
|
||
</html>
|