feat: unified context panel, remove tabs, context-aware layout

- 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>
This commit is contained in:
2026-03-26 09:51:49 +05:30
parent e6b2208077
commit 48ed300094
7 changed files with 2424 additions and 195 deletions

680
docs/generate-pptx.cjs Normal file
View File

@@ -0,0 +1,680 @@
/**
* Helix Engage — Weekly Update (Mar 1825, 2026)
* Light Mode PowerPoint Generator via PptxGenJS
*/
const PptxGenJS = require("pptxgenjs");
// ── Design Tokens (Light Mode) ─────────────────────────────────────────
const C = {
bg: "FFFFFF",
bgSubtle: "F8FAFC",
bgCard: "F1F5F9",
bgCardAlt: "E2E8F0",
text: "1E293B",
textSec: "475569",
textMuted: "94A3B8",
accent1: "0EA5E9", // Sky blue (telephony)
accent2: "8B5CF6", // Violet (server/backend)
accent3: "10B981", // Emerald (UX)
accent4: "F59E0B", // Amber (features)
accent5: "EF4444", // Rose (ops)
accent6: "6366F1", // Indigo (timeline)
white: "FFFFFF",
border: "CBD5E1",
};
const FONT = {
heading: "Arial",
body: "Arial",
};
// ── Helpers ──────────────────────────────────────────────────────────────
function addSlideNumber(slide, num, total) {
slide.addText(`${num} / ${total}`, {
x: 8.8, y: 5.2, w: 1.2, h: 0.3,
fontSize: 8, color: C.textMuted,
fontFace: FONT.body,
align: "right",
});
}
function addAccentBar(slide, color) {
slide.addShape("rect", {
x: 0, y: 0, w: 10, h: 0.06,
fill: { color },
});
}
function addLabel(slide, text, color, x, y) {
slide.addShape("roundRect", {
x, y, w: text.length * 0.09 + 0.4, h: 0.3,
fill: { color, transparency: 88 },
rectRadius: 0.15,
});
slide.addText(text.toUpperCase(), {
x, y, w: text.length * 0.09 + 0.4, h: 0.3,
fontSize: 7, fontFace: FONT.heading, bold: true,
color, align: "center", valign: "middle",
letterSpacing: 2,
});
}
function addCard(slide, opts) {
const { x, y, w, h, title, titleColor, items, badge } = opts;
// Card background
slide.addShape("roundRect", {
x, y, w, h,
fill: { color: C.bgCard },
line: { color: C.border, width: 0.5 },
rectRadius: 0.1,
});
// Title
const titleText = badge
? [{ text: title + " ", options: { bold: true, color: titleColor, fontSize: 11 } },
{ text: badge, options: { bold: true, color: C.white, fontSize: 7, highlight: titleColor } }]
: title;
slide.addText(titleText, {
x: x + 0.2, y: y + 0.08, w: w - 0.4, h: 0.35,
fontSize: 11, fontFace: FONT.heading, bold: true,
color: titleColor,
});
// Items as bullet list
if (items && items.length > 0) {
slide.addText(
items.map(item => ({
text: item,
options: {
fontSize: 8.5, fontFace: FONT.body, color: C.textSec,
bullet: { type: "bullet", style: "arabicPeriod" },
paraSpaceAfter: 2,
breakLine: true,
},
})),
{
x: x + 0.2, y: y + 0.4, w: w - 0.4, h: h - 0.5,
valign: "top",
bullet: { type: "bullet" },
lineSpacingMultiple: 1.1,
}
);
}
}
// ── Build Presentation ──────────────────────────────────────────────────
async function build() {
const pptx = new PptxGenJS();
pptx.layout = "LAYOUT_16x9";
pptx.author = "Satya Suman Sari";
pptx.company = "FortyTwo Platform";
pptx.title = "Helix Engage — Weekly Update (Mar 1825, 2026)";
pptx.subject = "Engineering Progress Report";
const TOTAL = 9;
// ═══════════════════════════════════════════════════════════════════════
// SLIDE 1 — Title
// ═══════════════════════════════════════════════════════════════════════
{
const slide = pptx.addSlide();
slide.background = { color: C.bg };
// Accent bar top
addAccentBar(slide, C.accent1);
// Decorative side stripe
slide.addShape("rect", {
x: 0, y: 0, w: 0.12, h: 5.63,
fill: { color: C.accent1 },
});
// Label
addLabel(slide, "Weekly Engineering Update", C.accent1, 3.0, 1.2);
// Title
slide.addText("Helix Engage", {
x: 1.0, y: 1.8, w: 8, h: 1.2,
fontSize: 44, fontFace: FONT.heading, bold: true,
color: C.accent1, align: "center",
});
// Subtitle
slide.addText("Contact Center CRM · Real-time Telephony · AI Copilot", {
x: 1.5, y: 2.9, w: 7, h: 0.5,
fontSize: 14, fontFace: FONT.body,
color: C.textSec, align: "center",
});
// Date
slide.addText("March 18 25, 2026", {
x: 3, y: 3.6, w: 4, h: 0.4,
fontSize: 12, fontFace: FONT.heading, bold: true,
color: C.textMuted, align: "center",
letterSpacing: 3,
});
// Bottom decoration
slide.addShape("rect", {
x: 3.5, y: 4.2, w: 3, h: 0.04,
fill: { color: C.accent2 },
});
// Author
slide.addText("Satya Suman Sari · FortyTwo Platform", {
x: 2, y: 4.5, w: 6, h: 0.35,
fontSize: 9, fontFace: FONT.body,
color: C.textMuted, align: "center",
});
addSlideNumber(slide, 1, TOTAL);
}
// ═══════════════════════════════════════════════════════════════════════
// SLIDE 2 — At a Glance
// ═══════════════════════════════════════════════════════════════════════
{
const slide = pptx.addSlide();
slide.background = { color: C.bg };
addAccentBar(slide, C.accent2);
addLabel(slide, "At a Glance", C.accent2, 0.5, 0.3);
slide.addText("Week in Numbers", {
x: 0.5, y: 0.65, w: 5, h: 0.5,
fontSize: 24, fontFace: FONT.heading, bold: true,
color: C.text,
});
// Stat cards
const stats = [
{ value: "78", label: "Total Commits", color: C.accent1 },
{ value: "3", label: "Repositories", color: C.accent2 },
{ value: "8", label: "Days Active", color: C.accent3 },
{ value: "50", label: "Frontend Commits", color: C.accent4 },
];
stats.forEach((s, i) => {
const x = 0.5 + i * 2.35;
// Card bg
slide.addShape("roundRect", {
x, y: 1.3, w: 2.1, h: 1.7,
fill: { color: C.bgCard },
line: { color: C.border, width: 0.5 },
rectRadius: 0.12,
});
// Accent top line
slide.addShape("rect", {
x: x + 0.2, y: 1.35, w: 1.7, h: 0.035,
fill: { color: s.color },
});
// Number
slide.addText(s.value, {
x, y: 1.5, w: 2.1, h: 0.9,
fontSize: 36, fontFace: FONT.heading, bold: true,
color: s.color, align: "center", valign: "middle",
});
// Label
slide.addText(s.label, {
x, y: 2.4, w: 2.1, h: 0.4,
fontSize: 9, fontFace: FONT.body,
color: C.textSec, align: "center",
});
});
// Repo breakdown pills
const repos = [
{ name: "helix-engage", count: "50", clr: C.accent1 },
{ name: "helix-engage-server", count: "27", clr: C.accent2 },
{ name: "FortyTwoApps/SDK", count: "1", clr: C.accent3 },
];
repos.forEach((r, i) => {
const x = 1.5 + i * 2.8;
slide.addShape("roundRect", {
x, y: 3.4, w: 2.5, h: 0.4,
fill: { color: C.bgCard },
line: { color: r.clr, width: 1 },
rectRadius: 0.2,
});
slide.addText(`${r.name} ${r.count}`, {
x, y: 3.4, w: 2.5, h: 0.4,
fontSize: 9, fontFace: FONT.heading, bold: true,
color: r.clr, align: "center", valign: "middle",
});
});
// Summary text
slide.addText("3 repos · 7 working days · 78 commits shipped to production", {
x: 1, y: 4.2, w: 8, h: 0.35,
fontSize: 10, fontFace: FONT.body, italic: true,
color: C.textMuted, align: "center",
});
addSlideNumber(slide, 2, TOTAL);
}
// ═══════════════════════════════════════════════════════════════════════
// SLIDE 3 — Telephony & SIP
// ═══════════════════════════════════════════════════════════════════════
{
const slide = pptx.addSlide();
slide.background = { color: C.bg };
addAccentBar(slide, C.accent1);
addLabel(slide, "Core Infrastructure", C.accent1, 0.5, 0.3);
slide.addText([
{ text: "☎ ", options: { fontSize: 22 } },
{ text: "Telephony & SIP Overhaul", options: { fontSize: 22, bold: true, color: C.text } },
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
addCard(slide, {
x: 0.3, y: 1.35, w: 4.5, h: 2.0,
title: "Outbound Calling", titleColor: C.accent1, badge: "Frontend",
items: [
"Direct SIP call from browser — no Kookoo bridge",
"Immediate call card UI with auto-answer SIP bridge",
"End Call label fix, force active state after auto-answer",
"Reset outboundPending on call end",
],
});
addCard(slide, {
x: 5.2, y: 1.35, w: 4.5, h: 2.0,
title: "Ozonetel Integration", titleColor: C.accent2, badge: "Server",
items: [
"Ozonetel V3 dial endpoint + webhook handler",
"Set Disposition API for ACW release",
"Force Ready endpoint for agent state mgmt",
"Token: 10-min cache, 401 invalidation, refresh on login",
],
});
addCard(slide, {
x: 0.3, y: 3.55, w: 4.5, h: 1.8,
title: "SIP & Agent State", titleColor: C.accent1, badge: "Frontend",
items: [
"SIP driven by Agent entity with token refresh",
"Centralised outbound dial into useSip().dialOutbound()",
"UCID tracking from SIP headers for disposition",
"Network indicator for connection health",
],
});
addCard(slide, {
x: 5.2, y: 3.55, w: 4.5, h: 1.8,
title: "Multi-Agent & Sessions", titleColor: C.accent2, badge: "Server",
items: [
"Multi-agent SIP with Redis session lockout",
"Strict duplicate login — one device per agent",
"Session lock stores IP + timestamp for debugging",
"SSE agent state broadcast for supervisor view",
],
});
addSlideNumber(slide, 3, TOTAL);
}
// ═══════════════════════════════════════════════════════════════════════
// SLIDE 4 — Call Desk & Agent UX
// ═══════════════════════════════════════════════════════════════════════
{
const slide = pptx.addSlide();
slide.background = { color: C.bg };
addAccentBar(slide, C.accent3);
addLabel(slide, "User Experience", C.accent3, 0.5, 0.3);
slide.addText([
{ text: "🖥 ", options: { fontSize: 22 } },
{ text: "Call Desk & Agent UX", options: { fontSize: 22, bold: true, color: C.text } },
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
addCard(slide, {
x: 0.3, y: 1.35, w: 3.05, h: 2.6,
title: "Call Desk Redesign", titleColor: C.accent3,
items: [
"2-panel layout with collapsible sidebar & inline AI",
"Collapsible context panel, worklist/calls tabs",
"Pinned header & chat input, numpad dialler",
"Ringtone support for incoming calls",
],
});
addCard(slide, {
x: 3.55, y: 1.35, w: 3.05, h: 2.6,
title: "Post-Call Workflow", titleColor: C.accent3,
items: [
"Disposition → appointment booking → follow-up",
"Disposition returns straight to worklist",
"Send disposition to sidecar with UCID for ACW",
"Enquiry in post-call, appointment skip button",
],
});
addCard(slide, {
x: 6.8, y: 1.35, w: 2.9, h: 2.6,
title: "UI Polish", titleColor: C.accent3,
items: [
"FontAwesome Pro Duotone icon migration",
"Tooltips, sticky headers, roles, search",
"Fix React error #520 in prod tables",
"AI scroll containment, brand tokens refresh",
],
});
addSlideNumber(slide, 4, TOTAL);
}
// ═══════════════════════════════════════════════════════════════════════
// SLIDE 5 — Features Shipped
// ═══════════════════════════════════════════════════════════════════════
{
const slide = pptx.addSlide();
slide.background = { color: C.bg };
addAccentBar(slide, C.accent4);
addLabel(slide, "Features Shipped", C.accent4, 0.5, 0.3);
slide.addText([
{ text: "🚀 ", options: { fontSize: 22 } },
{ text: "Major Features", options: { fontSize: 22, bold: true, color: C.text } },
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
addCard(slide, {
x: 0.3, y: 1.35, w: 4.5, h: 2.0,
title: "Supervisor Module", titleColor: C.accent4,
items: [
"Team performance analytics page",
"Live monitor with active calls visibility",
"Master data management pages",
"Server: team perf + active calls endpoints",
],
});
addCard(slide, {
x: 5.2, y: 1.35, w: 4.5, h: 2.0,
title: "Missed Call Queue (Phase 2)", titleColor: C.accent4,
items: [
"Missed call queue ingestion & worklist",
"Auto-assignment engine for agents",
"Login redesign with role-based routing",
"Lead lookup for missed callers",
],
});
addCard(slide, {
x: 0.3, y: 3.55, w: 4.5, h: 1.8,
title: "Agent Features (Phase 1)", titleColor: C.accent4,
items: [
"Agent status toggle (Ready / Not Ready / Break)",
"Global search across patients, leads, calls",
"Enquiry form for new patient intake",
"My Performance page + logout modal",
],
});
addCard(slide, {
x: 5.2, y: 3.55, w: 4.5, h: 1.8,
title: "Recording Analysis", titleColor: C.accent4,
items: [
"Deepgram diarization + AI insights",
"Redis caching layer for analysis results",
"Full-stack: frontend player + server module",
],
});
addSlideNumber(slide, 5, TOTAL);
}
// ═══════════════════════════════════════════════════════════════════════
// SLIDE 6 — Backend & Data
// ═══════════════════════════════════════════════════════════════════════
{
const slide = pptx.addSlide();
slide.background = { color: C.bg };
addAccentBar(slide, C.accent2);
addLabel(slide, "Backend & Data", C.accent2, 0.5, 0.3);
slide.addText([
{ text: "⚙ ", options: { fontSize: 22 } },
{ text: "Backend & Data Layer", options: { fontSize: 22, bold: true, color: C.text } },
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
addCard(slide, {
x: 0.3, y: 1.35, w: 4.5, h: 2.0,
title: "Platform Data Wiring", titleColor: C.accent2,
items: [
"Migrated frontend to Jotai + Vercel AI SDK",
"Corrected all 7 GraphQL queries (fields, LINKS/PHONES)",
"Webhook handler for Ozonetel call records",
"Complete seeder: 5 doctors, appointments linked",
],
});
addCard(slide, {
x: 5.2, y: 1.35, w: 4.5, h: 2.0,
title: "Server Endpoints", titleColor: C.accent2,
items: [
"Call control, recording, CDR, missed calls, live assist",
"Agent summary, AHT, performance aggregation",
"Token refresh endpoint for auto-renewal",
"Search module with full-text capabilities",
],
});
addCard(slide, {
x: 0.3, y: 3.55, w: 4.5, h: 1.8,
title: "Data Pages Built", titleColor: C.accent2,
items: [
"Worklist table, call history, patients, dashboard",
"Reports, team dashboard, campaigns, settings",
"Agent detail page, campaign edit slideout",
"Appointments page with data refresh on login",
],
});
addCard(slide, {
x: 5.2, y: 3.55, w: 4.5, h: 1.8,
title: "SDK App", titleColor: C.accent3, badge: "FortyTwoApps",
items: [
"Helix Engage SDK app entity definitions",
"Call center CRM object model for platform",
"Foundation for platform-native data integration",
],
});
addSlideNumber(slide, 6, TOTAL);
}
// ═══════════════════════════════════════════════════════════════════════
// SLIDE 7 — Deployment & Ops
// ═══════════════════════════════════════════════════════════════════════
{
const slide = pptx.addSlide();
slide.background = { color: C.bg };
addAccentBar(slide, C.accent5);
addLabel(slide, "Operations", C.accent5, 0.5, 0.3);
slide.addText([
{ text: "🛠 ", options: { fontSize: 22 } },
{ text: "Deployment & DevOps", options: { fontSize: 22, bold: true, color: C.text } },
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
addCard(slide, {
x: 0.3, y: 1.35, w: 3.05, h: 2.2,
title: "Deployment", titleColor: C.accent5,
items: [
"Deployed to Hostinger VPS with Docker",
"Switched to global_healthx Ozonetel account",
"Dockerfile for server-side containerization",
],
});
addCard(slide, {
x: 3.55, y: 1.35, w: 3.05, h: 2.2,
title: "AI & Testing", titleColor: C.accent5,
items: [
"Migrated AI to Vercel AI SDK + OpenAI provider",
"AI flow test script — validates full pipeline",
"Live call assist integration",
],
});
addCard(slide, {
x: 6.8, y: 1.35, w: 2.9, h: 2.2,
title: "Documentation", titleColor: C.accent5,
items: [
"Team onboarding README with arch guide",
"Supervisor module spec + plan",
"Multi-agent spec + plan",
"Next session plans in commits",
],
});
addSlideNumber(slide, 7, TOTAL);
}
// ═══════════════════════════════════════════════════════════════════════
// SLIDE 8 — Timeline
// ═══════════════════════════════════════════════════════════════════════
{
const slide = pptx.addSlide();
slide.background = { color: C.bg };
addAccentBar(slide, C.accent6);
addLabel(slide, "Day by Day", C.accent6, 0.5, 0.3);
slide.addText([
{ text: "📅 ", options: { fontSize: 22 } },
{ text: "Development Timeline", options: { fontSize: 22, bold: true, color: C.text } },
], { x: 0.5, y: 0.65, w: 9, h: 0.5, fontFace: FONT.heading });
const timeline = [
{ date: "MAR 18 (Tue)", title: "Foundation Day", desc: "Call desk redesign, Jotai + AI SDK migration, seeder, AI flow test, VPS deploy" },
{ date: "MAR 19 (Wed)", title: "Data Layer Sprint", desc: "All data pages, post-call workflow, GraphQL fixes, Kookoo IVR, outbound UI" },
{ date: "MAR 20 (Thu)", title: "Telephony Breakthrough", desc: "Direct SIP replacing Kookoo, UCID tracking, Force Ready, Set Disposition" },
{ date: "MAR 21 (Fri)", title: "Agent Experience", desc: "Phase 1: status toggle, search, enquiry form, My Performance, FA icons, AHT" },
{ date: "MAR 23 (Sun)", title: "Scale & Reliability", desc: "Phase 2: missed call queue, auto-assign, Redis lockout, Patient 360, SDK defs" },
{ date: "MAR 24 (Mon)", title: "Supervisor Module", desc: "Team perf, live monitor, master data, SSE, UUID fix, maintenance, QA sweep" },
{ date: "MAR 25 (Tue)", title: "Intelligence Layer", desc: "Deepgram diarization, AI insights, SIP via Agent entity, token refresh, network" },
];
// Vertical line
slide.addShape("rect", {
x: 1.4, y: 1.3, w: 0.025, h: 4.0,
fill: { color: C.accent6, transparency: 60 },
});
timeline.forEach((entry, i) => {
const y = 1.3 + i * 0.56;
// Dot
slide.addShape("ellipse", {
x: 1.32, y: y + 0.08, w: 0.18, h: 0.18,
fill: { color: C.accent6 },
line: { color: C.bg, width: 2 },
});
// Date
slide.addText(entry.date, {
x: 1.7, y: y, w: 1.6, h: 0.22,
fontSize: 7, fontFace: FONT.heading, bold: true,
color: C.accent6,
});
// Title
slide.addText(entry.title, {
x: 3.3, y: y, w: 2.0, h: 0.22,
fontSize: 9, fontFace: FONT.heading, bold: true,
color: C.text,
});
// Description
slide.addText(entry.desc, {
x: 5.3, y: y, w: 4.2, h: 0.45,
fontSize: 8, fontFace: FONT.body,
color: C.textSec,
valign: "top",
});
});
addSlideNumber(slide, 8, TOTAL);
}
// ═══════════════════════════════════════════════════════════════════════
// SLIDE 9 — Closing
// ═══════════════════════════════════════════════════════════════════════
{
const slide = pptx.addSlide();
slide.background = { color: C.bg };
addAccentBar(slide, C.accent3);
// Big headline
slide.addText("78 commits. 8 days. Ship mode.", {
x: 0.5, y: 1.4, w: 9, h: 0.8,
fontSize: 32, fontFace: FONT.heading, bold: true,
color: C.accent3, align: "center",
});
// Ship emoji
slide.addText("🚢", {
x: 4.2, y: 2.3, w: 1.6, h: 0.6,
fontSize: 28, align: "center",
});
// Description
slide.addText(
"From browser-native SIP calling to AI-powered recording analysis — Helix Engage is becoming a production contact center platform.",
{
x: 1.5, y: 3.0, w: 7, h: 0.6,
fontSize: 11, fontFace: FONT.body,
color: C.textSec, align: "center",
lineSpacingMultiple: 1.3,
}
);
// Achievement pills
const achievements = [
{ text: "SIP Calling ✓", color: C.accent1 },
{ text: "Multi-Agent ✓", color: C.accent2 },
{ text: "Supervisor ✓", color: C.accent3 },
{ text: "AI Copilot ✓", color: C.accent4 },
{ text: "Recording Analysis ✓", color: C.accent5 },
];
achievements.forEach((a, i) => {
const x = 0.8 + i * 1.8;
slide.addShape("roundRect", {
x, y: 3.9, w: 1.6, h: 0.35,
fill: { color: C.bgCard },
line: { color: a.color, width: 1 },
rectRadius: 0.17,
});
slide.addText(a.text, {
x, y: 3.9, w: 1.6, h: 0.35,
fontSize: 8, fontFace: FONT.heading, bold: true,
color: a.color, align: "center", valign: "middle",
});
});
// Author
slide.addText("Satya Suman Sari · FortyTwo Platform", {
x: 2, y: 4.7, w: 6, h: 0.3,
fontSize: 9, fontFace: FONT.body,
color: C.textMuted, align: "center",
});
addSlideNumber(slide, 9, TOTAL);
}
// ── Save ──────────────────────────────────────────────────────────────
const outPath = "weekly-update-mar18-25.pptx";
await pptx.writeFile({ fileName: outPath });
console.log(`✅ Presentation saved: ${outPath}`);
}
build().catch(err => {
console.error("❌ Failed:", err.message);
process.exit(1);
});