Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
35 changes: 33 additions & 2 deletions apps/app/src/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,15 +1,27 @@
"use client";

import { useEffect } from "react";
import { useEffect, useState } from "react";
import { ExampleLayout } from "@/components/example-layout";
import { useGenerativeUIExamples, useExampleSuggestions } from "@/hooks";
import { ExplainerCardsPortal } from "@/components/explainer-cards";
import { CopilotChat } from "@copilotkit/react-core/v2";
import { DemoGallery, type DemoItem } from "@/components/demo-gallery";
import { GridIcon } from "@/components/demo-gallery/grid-icon";
import { CopilotChat, useAgent, useCopilotKit } from "@copilotkit/react-core/v2";

export default function HomePage() {
useGenerativeUIExamples();
useExampleSuggestions();

const [demoDrawerOpen, setDemoDrawerOpen] = useState(false);
const { agent } = useAgent();
const { copilotkit } = useCopilotKit();

const handleTryDemo = (demo: DemoItem) => {
setDemoDrawerOpen(false);
agent.addMessage({ id: crypto.randomUUID(), content: demo.prompt, role: "user" });
copilotkit.runAgent({ agent });
};

// Widget bridge: handle messages from widget iframes
useEffect(() => {
const handler = (e: MessageEvent) => {
Expand Down Expand Up @@ -55,6 +67,20 @@ export default function HomePage() {
</p>
</div>
<div className="flex items-center gap-2">
<button
onClick={() => setDemoDrawerOpen(true)}
className="inline-flex items-center gap-1.5 px-3 py-2 rounded-full text-sm font-medium no-underline whitespace-nowrap transition-all duration-150 hover:-translate-y-px cursor-pointer"
style={{
color: "var(--text-secondary)",
border: "1px solid var(--color-border-glass, rgba(0,0,0,0.1))",
background: "var(--surface-primary, rgba(255,255,255,0.6))",
fontFamily: "var(--font-family)",
}}
title="Open Demo Gallery"
>
<GridIcon size={15} />
Demos
</button>
<a
href="https://github.com/CopilotKit/OpenGenerativeUI"
target="_blank"
Expand Down Expand Up @@ -84,6 +110,11 @@ export default function HomePage() {
</div>
</div>

<DemoGallery
open={demoDrawerOpen}
onClose={() => setDemoDrawerOpen(false)}
onTryDemo={handleTryDemo}
/>
</>
);
}
39 changes: 39 additions & 0 deletions apps/app/src/components/demo-gallery/category-filter.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
"use client";

import type { DemoCategory } from "./demo-data";
import { DEMO_CATEGORIES } from "./demo-data";

interface CategoryFilterProps {
selected: DemoCategory | null;
onSelect: (category: DemoCategory | null) => void;
}

export function CategoryFilter({ selected, onSelect }: CategoryFilterProps) {
const categories: (DemoCategory | null)[] = [null, ...DEMO_CATEGORIES];

return (
<div className="flex gap-2 overflow-x-auto pb-1 scrollbar-none">
{categories.map((cat) => {
const isActive = cat === selected;
return (
<button
key={cat ?? "all"}
onClick={() => onSelect(cat)}
className="shrink-0 px-3 py-1.5 rounded-full text-xs font-medium transition-all duration-150 cursor-pointer"
style={{
background: isActive
? "linear-gradient(135deg, var(--color-lilac-dark, #6366f1), var(--color-mint-dark, #10b981))"
: "var(--surface-primary, rgba(255,255,255,0.6))",
color: isActive ? "#fff" : "var(--text-secondary, #666)",
border: isActive
? "none"
: "1px solid var(--color-border-glass, rgba(0,0,0,0.1))",
}}
>
{cat ?? "All"}
</button>
);
})}
</div>
);
}
63 changes: 63 additions & 0 deletions apps/app/src/components/demo-gallery/demo-card.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
"use client";

import type { DemoItem } from "./demo-data";

const CATEGORY_COLORS: Record<string, { bg: string; text: string }> = {
"3D / Animation": { bg: "rgba(139,92,246,0.12)", text: "rgba(139,92,246,1)" },
"Data Visualization": { bg: "rgba(59,130,246,0.12)", text: "rgba(59,130,246,1)" },
Diagrams: { bg: "rgba(16,185,129,0.12)", text: "rgba(16,185,129,1)" },
Interactive: { bg: "rgba(245,158,11,0.12)", text: "rgba(245,158,11,1)" },
"UI Components": { bg: "rgba(236,72,153,0.12)", text: "rgba(236,72,153,1)" },
};

interface DemoCardProps {
demo: DemoItem;
onTry: (demo: DemoItem) => void;
}

export function DemoCard({ demo, onTry }: DemoCardProps) {
const categoryColor = CATEGORY_COLORS[demo.category] ?? {
bg: "rgba(100,100,100,0.12)",
text: "rgba(100,100,100,1)",
};

return (
<button
onClick={() => onTry(demo)}
className="rounded-xl overflow-hidden flex flex-col text-left transition-all duration-200 hover:shadow-lg hover:-translate-y-0.5 cursor-pointer w-full"
style={{
border: "1px solid var(--color-border-glass, rgba(0,0,0,0.1))",
background: "var(--surface-primary, #fff)",
}}
>
<div className="flex flex-col gap-1.5 p-4 flex-1">
<div className="flex items-center justify-between">
<div className="flex items-center gap-2">
<span className="text-lg">{demo.emoji}</span>
<h3
className="text-sm font-semibold truncate"
style={{ color: "var(--text-primary, #1a1a1a)" }}
>
{demo.title}
</h3>
</div>
<span
className="text-[10px] font-semibold px-2 py-0.5 rounded-full shrink-0"
style={{
background: categoryColor.bg,
color: categoryColor.text,
}}
>
{demo.category}
</span>
</div>
<p
className="text-xs line-clamp-2"
style={{ color: "var(--text-secondary, #666)" }}
>
{demo.description}
</p>
</div>
</button>
);
}
126 changes: 126 additions & 0 deletions apps/app/src/components/demo-gallery/demo-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
export type DemoCategory =
| "3D / Animation"
| "Data Visualization"
| "Diagrams"
| "Interactive"
| "UI Components";

export interface DemoItem {
id: string;
title: string;
description: string;
category: DemoCategory;
emoji: string;
prompt: string;
}

export const DEMO_EXAMPLES: DemoItem[] = [
{
id: "demo-pitch-roll-yaw",
title: "Pitch, Roll & Yaw",
description:
"Interactive 3D airplane explaining pitch, roll, and yaw with control buttons",
category: "3D / Animation",
emoji: "✈️",
prompt:
"Create a 3D plane in Three.js to explain how pitch, roll, and yaw work. Give me buttons to control each axis. Add labels showing which rotation is which.",
},
{
id: "demo-weather",
title: "Weather Card",
description:
"Current weather conditions with temperature, humidity, wind, and UV index",
category: "UI Components",
emoji: "🌤️",
prompt:
"Create a beautiful weather card showing current conditions for San Francisco with temperature, humidity, wind speed, UV index, and a 5-day mini forecast.",
},
{
id: "demo-binary-search",
title: "Binary Search",
description:
"Step-by-step visualization of binary search on a sorted array",
category: "Diagrams",
emoji: "🔍",
prompt:
"Visualize how binary search works on a sorted list. Step by step with animation. Show the high, low, and mid pointers moving.",
},
{
id: "demo-solar-system",
title: "Solar System",
description:
"3D solar system with orbiting planets you can click for facts",
category: "3D / Animation",
emoji: "🪐",
prompt:
"Build a 3D solar system with orbiting planets using Three.js. Let me click on each planet to see facts about it. Include realistic relative sizes and orbital speeds.",
},
{
id: "demo-dashboard",
title: "KPI Dashboard",
description:
"Quarterly performance dashboard with metrics cards and bar chart",
category: "Data Visualization",
emoji: "📊",
prompt:
"Create a KPI dashboard showing Q1 2026 performance with revenue, active users, and conversion rate. Include a monthly revenue bar chart and trend indicators.",
},
{
id: "demo-sorting",
title: "Sorting Comparison",
description:
"Animated side-by-side comparison of bubble sort vs quicksort",
category: "Diagrams",
emoji: "📶",
prompt:
"Create an animated comparison of bubble sort vs quicksort running side by side on the same random array. Add speed controls and a step counter.",
},
{
id: "demo-pomodoro",
title: "Pomodoro Timer",
description:
"Focus timer with circular progress ring, session counter, and controls",
category: "Interactive",
emoji: "🍅",
prompt:
"Build a Pomodoro timer with a circular progress ring, start/pause/reset buttons, and a session counter. Use 25 min work / 5 min break intervals. Make it look clean and minimal.",
},
{
id: "demo-neural-network",
title: "Neural Network",
description:
"Interactive neural network diagram with animated forward pass",
category: "Diagrams",
emoji: "🧠",
prompt:
"Visualize a simple neural network with input, hidden, and output layers. Animate the forward pass showing data flowing through the network. Let me adjust the number of neurons per layer.",
},
{
id: "demo-invoice",
title: "Invoice Card",
description:
"Compact invoice card with amount, client info, and action buttons",
category: "UI Components",
emoji: "🧾",
prompt:
"Create an invoice card showing a monthly billing summary with client name, amount due, invoice number, and send/expand action buttons.",
},
{
id: "demo-music-visualizer",
title: "Music Equalizer",
description:
"Audio equalizer visualization with animated frequency bars and controls",
category: "3D / Animation",
emoji: "🎵",
prompt:
"Create a music equalizer visualization with animated bars that respond to frequency sliders. Add controls for bass, mid, and treble. Use a gradient color scheme.",
},
];

export const DEMO_CATEGORIES: DemoCategory[] = [
"3D / Animation",
"Data Visualization",
"Diagrams",
"Interactive",
"UI Components",
];
27 changes: 27 additions & 0 deletions apps/app/src/components/demo-gallery/grid-icon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import type { CSSProperties } from "react";

interface GridIconProps {
size?: number;
style?: CSSProperties;
}

export function GridIcon({ size = 18, style }: GridIconProps) {
return (
<svg
width={size}
height={size}
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
style={style}
>
<rect width="7" height="7" x="3" y="3" rx="1" />
<rect width="7" height="7" x="14" y="3" rx="1" />
<rect width="7" height="7" x="14" y="14" rx="1" />
<rect width="7" height="7" x="3" y="14" rx="1" />
</svg>
);
}
Loading
Loading