"use client";
import { useEffect, useState } from "react";
import { createPortal } from "react-dom";
const cards = [
{
icon: (
),
title: "Generative UI",
description:
"AI generates interactive charts, visualizations, and rich components directly in the conversation.",
},
{
icon: (
),
title: "Interactive Widgets",
description:
"Complex HTML/JS visualizations run in sandboxed iframes — try asking for an animation or diagram.",
},
{
icon: (
),
title: "Visualize Anything",
description:
"Ask for algorithm visualizations, 3D animations, diagrams, or any interactive visual explanation.",
},
];
function ExplainerCards() {
return (
{cards.map((card) => (
))}
);
}
/**
* Portal that injects ExplainerCards into the CopilotKit welcome screen.
* Inserts a wrapper div inside the welcome screen's main content area,
* positioned before the suggestion pills. Auto-removes when the welcome
* screen disappears (user sends a message).
*/
export function ExplainerCardsPortal() {
const [portalTarget, setPortalTarget] = useState(null);
useEffect(() => {
const WELCOME_SELECTOR = '[data-testid="copilot-welcome-screen"]';
const PORTAL_ID = "explainer-cards-portal";
const tryAttach = () => {
const welcomeScreen = document.querySelector(WELCOME_SELECTOR);
if (!welcomeScreen) {
setPortalTarget(null);
return;
}
// Reuse existing portal container if present
let portal = document.getElementById(PORTAL_ID);
if (portal) {
setPortalTarget(portal);
return;
}
// Insert portal container inside the welcome screen's main content div,
// before the suggestions row
const mainContent = welcomeScreen.children[0] as HTMLElement | undefined;
if (!mainContent) return;
portal = document.createElement("div");
portal.id = PORTAL_ID;
portal.style.width = "100%";
// Insert before the last child (suggestions row)
const suggestionsRow = mainContent.lastElementChild;
if (suggestionsRow) {
mainContent.insertBefore(portal, suggestionsRow);
} else {
mainContent.appendChild(portal);
}
setPortalTarget(portal);
};
tryAttach();
const observer = new MutationObserver(() => {
const welcomeScreen = document.querySelector(WELCOME_SELECTOR);
if (!welcomeScreen) {
// Welcome screen removed (chat started) — clean up
const stale = document.getElementById(PORTAL_ID);
if (stale) stale.remove();
setPortalTarget(null);
} else if (!document.getElementById(PORTAL_ID)) {
// Welcome screen appeared but no portal yet
tryAttach();
}
});
observer.observe(document.body, { childList: true, subtree: true });
return () => observer.disconnect();
}, []);
if (!portalTarget) return null;
return createPortal(, portalTarget);
}