Skip to content

Commit 2bdec5d

Browse files
committed
Merge branch 'claude/youthful-engelbart'
2 parents 77eeafa + 07a14df commit 2bdec5d

3 files changed

Lines changed: 221 additions & 10 deletions

File tree

apps/app/src/app/globals.css

Lines changed: 58 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -540,9 +540,64 @@ body, html {
540540
display: none !important;
541541
}
542542

543-
/* Hide default welcome message */
544-
[data-testid="copilot-welcome-screen"] [data-testid="copilot-welcome-message"] {
545-
display: none !important;
543+
/* "Try these Prompts" label above suggestion pills */
544+
[data-testid="copilot-suggestions"] {
545+
flex-wrap: wrap !important;
546+
justify-content: center !important;
547+
}
548+
549+
[data-testid="copilot-suggestions"]::before {
550+
content: "Try these Prompts";
551+
display: block;
552+
width: 100%;
553+
text-align: center;
554+
font-size: 12px;
555+
font-weight: 500;
556+
color: var(--text-tertiary);
557+
letter-spacing: 0.02em;
558+
margin-bottom: 2px;
559+
}
560+
561+
/* === Explainer Cards === */
562+
.explainer-cards {
563+
display: grid;
564+
grid-template-columns: repeat(3, 1fr);
565+
gap: 12px;
566+
padding: 0 4px;
567+
max-width: 820px;
568+
margin: 4px auto 12px;
569+
width: 100%;
570+
}
571+
572+
@media (max-width: 640px) {
573+
.explainer-cards {
574+
grid-template-columns: 1fr;
575+
padding: 0 8px;
576+
}
577+
}
578+
579+
.explainer-card {
580+
background: var(--color-glass-subtle);
581+
backdrop-filter: blur(8px);
582+
-webkit-backdrop-filter: blur(8px);
583+
border: 1px solid var(--color-border-glass);
584+
border-radius: var(--radius-lg);
585+
padding: 14px 16px;
586+
display: flex;
587+
flex-direction: column;
588+
gap: 8px;
589+
}
590+
591+
.explainer-card-icon {
592+
display: flex;
593+
align-items: center;
594+
justify-content: center;
595+
width: 28px;
596+
height: 28px;
597+
border-radius: 8px;
598+
background: linear-gradient(135deg, var(--color-lilac), var(--color-mint));
599+
color: #fff;
600+
flex-shrink: 0;
546601
}
547602

548603
/* === Flash Animation === */

apps/app/src/app/page.tsx

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useEffect } from "react";
44
import { ExampleLayout } from "@/components/example-layout";
55
import { useGenerativeUIExamples, useExampleSuggestions } from "@/hooks";
6+
import { ExplainerCardsPortal } from "@/components/explainer-cards";
67

78
import { CopilotChat } from "@copilotkit/react-core/v2";
89

@@ -38,21 +39,21 @@ export default function HomePage() {
3839
background: "linear-gradient(135deg, rgba(190,194,255,0.08) 0%, rgba(133,224,206,0.06) 100%)",
3940
}}
4041
>
41-
<div className="flex items-center justify-between gap-4 px-5 py-2.5">
42-
<div className="flex items-center gap-2.5 min-w-0 flex-1">
42+
<div className="flex items-center justify-between gap-4 px-5 py-3">
43+
<div className="flex items-center gap-3 min-w-0 flex-1">
4344
<div
44-
className="flex items-center justify-center shrink-0 w-7 h-7 rounded-lg text-white"
45+
className="flex items-center justify-center shrink-0 w-9 h-9 rounded-lg text-white"
4546
style={{
4647
background: "linear-gradient(135deg, var(--color-lilac), var(--color-mint))",
4748
}}
4849
>
49-
<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
50+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
5051
<path d="M12 2L2 7l10 5 10-5-10-5z" />
5152
<path d="M2 17l10 5 10-5" />
5253
<path d="M2 12l10 5 10-5" />
5354
</svg>
5455
</div>
55-
<p className="text-[13px] font-semibold m-0 leading-snug" style={{ color: "var(--text-primary)" }}>
56+
<p className="text-base font-semibold m-0 leading-snug" style={{ color: "var(--text-primary)" }}>
5657
Open Generative UI
5758
<span className="font-normal" style={{ color: "var(--text-secondary)" }}> — powered by CopilotKit</span>
5859
</p>
@@ -61,7 +62,7 @@ export default function HomePage() {
6162
href="https://github.com/CopilotKit/OpenGenerativeUI.git"
6263
target="_blank"
6364
rel="noopener noreferrer"
64-
className="inline-flex items-center px-3.5 py-1.5 rounded-full text-xs font-semibold text-white no-underline whitespace-nowrap transition-all duration-150 hover:-translate-y-px"
65+
className="inline-flex items-center px-5 py-2 rounded-full text-sm font-semibold text-white no-underline whitespace-nowrap transition-all duration-150 hover:-translate-y-px"
6566
style={{
6667
background: "linear-gradient(135deg, var(--color-lilac-dark), var(--color-mint-dark))",
6768
boxShadow: "0 1px 4px rgba(149,153,204,0.3)",
@@ -73,7 +74,14 @@ export default function HomePage() {
7374
</div>
7475
</div>
7576

76-
<ExampleLayout chatContent={<CopilotChat />} />
77+
<ExampleLayout chatContent={
78+
<CopilotChat
79+
labels={{
80+
welcomeMessageText: "What do you want to visualize today?",
81+
}}
82+
/>
83+
} />
84+
<ExplainerCardsPortal />
7785
</div>
7886
</div>
7987
</>
Lines changed: 148 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,148 @@
1+
"use client";
2+
3+
import { useEffect, useState } from "react";
4+
import { createPortal } from "react-dom";
5+
6+
const cards = [
7+
{
8+
icon: (
9+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
10+
<path d="M21.21 15.89A10 10 0 1 1 8 2.83" />
11+
<path d="M22 12A10 10 0 0 0 12 2v10z" />
12+
</svg>
13+
),
14+
title: "Generative UI",
15+
description:
16+
"AI generates interactive charts, visualizations, and rich components directly in the conversation.",
17+
},
18+
{
19+
icon: (
20+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
21+
<rect x="2" y="3" width="20" height="14" rx="2" ry="2" />
22+
<line x1="8" y1="21" x2="16" y2="21" />
23+
<line x1="12" y1="17" x2="12" y2="21" />
24+
</svg>
25+
),
26+
title: "Interactive Widgets",
27+
description:
28+
"Complex HTML/JS visualizations run in sandboxed iframes — try asking for an animation or diagram.",
29+
},
30+
{
31+
icon: (
32+
<svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
33+
<polygon points="12 2 2 7 12 12 22 7 12 2" />
34+
<polyline points="2 17 12 22 22 17" />
35+
<polyline points="2 12 12 17 22 12" />
36+
</svg>
37+
),
38+
title: "Visualize Anything",
39+
description:
40+
"Ask for algorithm visualizations, 3D animations, diagrams, or any interactive visual explanation.",
41+
},
42+
];
43+
44+
function ExplainerCards() {
45+
return (
46+
<div className="explainer-cards">
47+
{cards.map((card) => (
48+
<div key={card.title} className="explainer-card">
49+
<div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
50+
<div className="explainer-card-icon">
51+
{card.icon}
52+
</div>
53+
<span
54+
style={{
55+
fontSize: "13px",
56+
fontWeight: 600,
57+
color: "var(--text-primary)",
58+
}}
59+
>
60+
{card.title}
61+
</span>
62+
</div>
63+
<p
64+
style={{
65+
fontSize: "12px",
66+
lineHeight: 1.5,
67+
color: "var(--text-secondary)",
68+
margin: 0,
69+
}}
70+
>
71+
{card.description}
72+
</p>
73+
</div>
74+
))}
75+
</div>
76+
);
77+
}
78+
79+
/**
80+
* Portal that injects ExplainerCards into the CopilotKit welcome screen.
81+
* Inserts a wrapper div inside the welcome screen's main content area,
82+
* positioned before the suggestion pills. Auto-removes when the welcome
83+
* screen disappears (user sends a message).
84+
*/
85+
export function ExplainerCardsPortal() {
86+
const [portalTarget, setPortalTarget] = useState<HTMLElement | null>(null);
87+
88+
useEffect(() => {
89+
const WELCOME_SELECTOR = '[data-testid="copilot-welcome-screen"]';
90+
const PORTAL_ID = "explainer-cards-portal";
91+
92+
const tryAttach = () => {
93+
const welcomeScreen = document.querySelector<HTMLElement>(WELCOME_SELECTOR);
94+
if (!welcomeScreen) {
95+
setPortalTarget(null);
96+
return;
97+
}
98+
99+
// Reuse existing portal container if present
100+
let portal = document.getElementById(PORTAL_ID);
101+
if (portal) {
102+
setPortalTarget(portal);
103+
return;
104+
}
105+
106+
// Insert portal container inside the welcome screen's main content div,
107+
// before the suggestions row
108+
const mainContent = welcomeScreen.children[0] as HTMLElement | undefined;
109+
if (!mainContent) return;
110+
111+
portal = document.createElement("div");
112+
portal.id = PORTAL_ID;
113+
portal.style.width = "100%";
114+
115+
// Insert before the last child (suggestions row)
116+
const suggestionsRow = mainContent.lastElementChild;
117+
if (suggestionsRow) {
118+
mainContent.insertBefore(portal, suggestionsRow);
119+
} else {
120+
mainContent.appendChild(portal);
121+
}
122+
123+
setPortalTarget(portal);
124+
};
125+
126+
tryAttach();
127+
128+
const observer = new MutationObserver(() => {
129+
const welcomeScreen = document.querySelector<HTMLElement>(WELCOME_SELECTOR);
130+
if (!welcomeScreen) {
131+
// Welcome screen removed (chat started) — clean up
132+
const stale = document.getElementById(PORTAL_ID);
133+
if (stale) stale.remove();
134+
setPortalTarget(null);
135+
} else if (!document.getElementById(PORTAL_ID)) {
136+
// Welcome screen appeared but no portal yet
137+
tryAttach();
138+
}
139+
});
140+
141+
observer.observe(document.body, { childList: true, subtree: true });
142+
return () => observer.disconnect();
143+
}, []);
144+
145+
if (!portalTarget) return null;
146+
147+
return createPortal(<ExplainerCards />, portalTarget);
148+
}

0 commit comments

Comments
 (0)