Skip to content

Commit 3060930

Browse files
authored
Merge pull request #61 from CopilotKit/hide-templates-feature
Hide templates feature from UI
2 parents a832460 + c6ef493 commit 3060930

13 files changed

Lines changed: 468 additions & 74 deletions

File tree

apps/agent/main.py

Lines changed: 1 addition & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -18,13 +18,12 @@
1818
from src.query import query_data
1919
from src.todos import AgentState, todo_tools
2020
from src.form import generate_form
21-
from src.templates import template_tools
2221

2322
load_dotenv()
2423

2524
agent = create_deep_agent(
2625
model=ChatOpenAI(model=os.environ.get("LLM_MODEL", "gpt-5.4-2026-03-05")),
27-
tools=[query_data, *todo_tools, generate_form, *template_tools],
26+
tools=[query_data, *todo_tools, generate_form],
2827
middleware=[CopilotKitMiddleware()],
2928
context_schema=AgentState,
3029
skills=[str(Path(__file__).parent / "skills")],
@@ -66,28 +65,6 @@
6665
**Quality bar**: Every visualization should look polished and portfolio-ready. Use smooth animations, proper lighting (ambient + directional at minimum), responsive canvas sizing (`window.addEventListener('resize', ...)`), and antialiasing (`antialias: true`). No proof-of-concept quality.
6766
6867
**Critical**: `<script type="module">` is REQUIRED when using import map libraries. Regular `<script>` tags cannot use `import` statements.
69-
70-
## UI Templates
71-
72-
Users can save generated UIs as reusable templates and apply them later.
73-
You have backend tools: `save_template`, `list_templates`, `apply_template`, `delete_template`.
74-
75-
**When a user asks to apply/recreate a template with new data:**
76-
Check `pending_template` in state — the frontend sets this when the user picks a template.
77-
If `pending_template` is present (has `id` and `name`):
78-
1. Call `apply_template(template_id=pending_template["id"])` to retrieve the HTML
79-
2. Take the returned HTML and COPY IT EXACTLY, only replacing the data values
80-
(names, numbers, dates, labels, amounts) to match the user's message
81-
3. Render the modified HTML using `widgetRenderer`
82-
4. Call `clear_pending_template` to reset the pending state
83-
84-
If no `pending_template` is set but the user mentions a template by name, use
85-
`apply_template(name="...")` instead.
86-
87-
CRITICAL: Do NOT rewrite or generate HTML from scratch. Take the original HTML string,
88-
find-and-replace ONLY the data values, and pass the result to widgetRenderer.
89-
This preserves the exact layout and styling of the original template.
90-
For bar/pie chart templates, use `barChart` or `pieChart` component instead.
9168
""",
9269
)
9370

apps/agent/src/todos.py

Lines changed: 1 addition & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -2,26 +2,18 @@
22
from langchain.tools import ToolRuntime, tool
33
from langchain.messages import ToolMessage
44
from langgraph.types import Command
5-
from typing import Optional, TypedDict, Literal
5+
from typing import TypedDict, Literal
66
import uuid
77

8-
from src.templates import UITemplate
9-
108
class Todo(TypedDict):
119
id: str
1210
title: str
1311
description: str
1412
emoji: str
1513
status: Literal["pending", "completed"]
1614

17-
class PendingTemplate(TypedDict, total=False):
18-
id: str
19-
name: str
20-
2115
class AgentState(BaseAgentState):
2216
todos: list[Todo]
23-
templates: list[UITemplate]
24-
pending_template: Optional[PendingTemplate]
2517

2618
@tool
2719
def manage_todos(todos: list[Todo], runtime: ToolRuntime) -> Command:

apps/app/src/app/page.tsx

Lines changed: 22 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -4,16 +4,23 @@ import { useEffect, useState } from "react";
44
import { ExampleLayout } from "@/components/example-layout";
55
import { useGenerativeUIExamples, useExampleSuggestions } from "@/hooks";
66
import { ExplainerCardsPortal } from "@/components/explainer-cards";
7-
import { TemplateLibrary } from "@/components/template-library";
8-
import { TemplateChip } from "@/components/template-library/template-chip";
7+
import { DemoGallery, type DemoItem } from "@/components/demo-gallery";
98

109
import { CopilotChat } from "@copilotkit/react-core/v2";
10+
import { useCopilotChat } from "@copilotkit/react-core";
1111

1212
export default function HomePage() {
1313
useGenerativeUIExamples();
1414
useExampleSuggestions();
1515

16-
const [templateDrawerOpen, setTemplateDrawerOpen] = useState(false);
16+
const [demoDrawerOpen, setDemoDrawerOpen] = useState(false);
17+
const { appendMessage } = useCopilotChat();
18+
19+
const handleTryDemo = (demo: DemoItem) => {
20+
setDemoDrawerOpen(false);
21+
// eslint-disable-next-line @typescript-eslint/no-explicit-any
22+
appendMessage({ content: demo.prompt, role: "user" } as any);
23+
};
1724

1825
// Widget bridge: handle messages from widget iframes
1926
useEffect(() => {
@@ -60,22 +67,24 @@ export default function HomePage() {
6067
</p>
6168
</div>
6269
<div className="flex items-center gap-2">
63-
{/* Template Library toggle */}
6470
<button
65-
onClick={() => setTemplateDrawerOpen(true)}
66-
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"
71+
onClick={() => setDemoDrawerOpen(true)}
72+
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"
6773
style={{
6874
color: "var(--text-secondary)",
6975
border: "1px solid var(--color-border-glass, rgba(0,0,0,0.1))",
7076
background: "var(--surface-primary, rgba(255,255,255,0.6))",
7177
fontFamily: "var(--font-family)",
7278
}}
73-
title="Open Template Library"
79+
title="Open Demo Gallery"
7480
>
7581
<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
76-
<path d="m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z" />
82+
<rect width="7" height="7" x="3" y="3" rx="1" />
83+
<rect width="7" height="7" x="14" y="3" rx="1" />
84+
<rect width="7" height="7" x="14" y="14" rx="1" />
85+
<rect width="7" height="7" x="3" y="14" rx="1" />
7786
</svg>
78-
Templates
87+
Demos
7988
</button>
8089
<a
8190
href="https://github.com/CopilotKit/OpenGenerativeUI"
@@ -105,13 +114,10 @@ export default function HomePage() {
105114
</div>
106115
</div>
107116

108-
{/* Template chip — portal renders above chat input */}
109-
<TemplateChip />
110-
111-
{/* Template Library Drawer */}
112-
<TemplateLibrary
113-
open={templateDrawerOpen}
114-
onClose={() => setTemplateDrawerOpen(false)}
117+
<DemoGallery
118+
open={demoDrawerOpen}
119+
onClose={() => setDemoDrawerOpen(false)}
120+
onTryDemo={handleTryDemo}
115121
/>
116122
</>
117123
);
Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
"use client";
2+
3+
import type { DemoCategory } from "./demo-data";
4+
import { DEMO_CATEGORIES } from "./demo-data";
5+
6+
interface CategoryFilterProps {
7+
selected: DemoCategory | null;
8+
onSelect: (category: DemoCategory | null) => void;
9+
}
10+
11+
export function CategoryFilter({ selected, onSelect }: CategoryFilterProps) {
12+
const categories: (DemoCategory | null)[] = [null, ...DEMO_CATEGORIES];
13+
14+
return (
15+
<div className="flex gap-2 overflow-x-auto pb-1 scrollbar-none">
16+
{categories.map((cat) => {
17+
const isActive = cat === selected;
18+
return (
19+
<button
20+
key={cat ?? "all"}
21+
onClick={() => onSelect(cat)}
22+
className="shrink-0 px-3 py-1.5 rounded-full text-xs font-medium transition-all duration-150 cursor-pointer"
23+
style={{
24+
background: isActive
25+
? "linear-gradient(135deg, var(--color-lilac-dark, #6366f1), var(--color-mint-dark, #10b981))"
26+
: "var(--surface-primary, rgba(255,255,255,0.6))",
27+
color: isActive ? "#fff" : "var(--text-secondary, #666)",
28+
border: isActive
29+
? "none"
30+
: "1px solid var(--color-border-glass, rgba(0,0,0,0.1))",
31+
}}
32+
>
33+
{cat ?? "All"}
34+
</button>
35+
);
36+
})}
37+
</div>
38+
);
39+
}
Lines changed: 109 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,109 @@
1+
"use client";
2+
3+
import type { DemoItem } from "./demo-data";
4+
5+
// Category badge colors
6+
const CATEGORY_COLORS: Record<string, { bg: string; text: string }> = {
7+
"3D / Animation": { bg: "rgba(139,92,246,0.12)", text: "rgba(139,92,246,1)" },
8+
"Data Visualization": { bg: "rgba(59,130,246,0.12)", text: "rgba(59,130,246,1)" },
9+
Diagrams: { bg: "rgba(16,185,129,0.12)", text: "rgba(16,185,129,1)" },
10+
Interactive: { bg: "rgba(245,158,11,0.12)", text: "rgba(245,158,11,1)" },
11+
"UI Components": { bg: "rgba(236,72,153,0.12)", text: "rgba(236,72,153,1)" },
12+
};
13+
14+
// Emoji background gradients
15+
const EMOJI_GRADIENTS: Record<string, string> = {
16+
"3D / Animation":
17+
"linear-gradient(135deg, rgba(139,92,246,0.08) 0%, rgba(59,130,246,0.06) 100%)",
18+
"Data Visualization":
19+
"linear-gradient(135deg, rgba(59,130,246,0.08) 0%, rgba(16,185,129,0.06) 100%)",
20+
Diagrams:
21+
"linear-gradient(135deg, rgba(16,185,129,0.08) 0%, rgba(59,130,246,0.06) 100%)",
22+
Interactive:
23+
"linear-gradient(135deg, rgba(245,158,11,0.08) 0%, rgba(236,72,153,0.06) 100%)",
24+
"UI Components":
25+
"linear-gradient(135deg, rgba(236,72,153,0.08) 0%, rgba(139,92,246,0.06) 100%)",
26+
};
27+
28+
interface DemoCardProps {
29+
demo: DemoItem;
30+
onTry: (demo: DemoItem) => void;
31+
}
32+
33+
export function DemoCard({ demo, onTry }: DemoCardProps) {
34+
const categoryColor = CATEGORY_COLORS[demo.category] ?? {
35+
bg: "rgba(100,100,100,0.12)",
36+
text: "rgba(100,100,100,1)",
37+
};
38+
39+
return (
40+
<div
41+
className="rounded-xl overflow-hidden flex flex-col transition-all duration-200 hover:shadow-lg hover:-translate-y-0.5"
42+
style={{
43+
border: "1px solid var(--color-border-glass, rgba(0,0,0,0.1))",
44+
background: "var(--surface-primary, #fff)",
45+
}}
46+
>
47+
{/* Preview area */}
48+
<div
49+
className="relative overflow-hidden"
50+
style={{
51+
height: 160,
52+
background:
53+
EMOJI_GRADIENTS[demo.category] ??
54+
"var(--color-background-secondary)",
55+
}}
56+
>
57+
<div className="flex items-center justify-center h-full">
58+
<span className="text-5xl" role="img" aria-label={demo.title}>
59+
{demo.emoji}
60+
</span>
61+
</div>
62+
63+
{/* Category badge */}
64+
<span
65+
className="absolute top-2 right-2 text-[10px] font-semibold px-2 py-0.5 rounded-full"
66+
style={{
67+
background: categoryColor.bg,
68+
color: categoryColor.text,
69+
}}
70+
>
71+
{demo.category}
72+
</span>
73+
</div>
74+
75+
{/* Info */}
76+
<div className="flex flex-col gap-1 p-3 flex-1">
77+
<div className="flex items-center gap-2">
78+
<span className="text-base">{demo.emoji}</span>
79+
<h3
80+
className="text-sm font-semibold truncate"
81+
style={{ color: "var(--text-primary, #1a1a1a)" }}
82+
>
83+
{demo.title}
84+
</h3>
85+
</div>
86+
<p
87+
className="text-xs line-clamp-2"
88+
style={{ color: "var(--text-secondary, #666)" }}
89+
>
90+
{demo.description}
91+
</p>
92+
</div>
93+
94+
{/* Action */}
95+
<div className="p-3 pt-0">
96+
<button
97+
onClick={() => onTry(demo)}
98+
className="w-full text-xs font-medium py-2 rounded-lg transition-all duration-150 hover:scale-[1.02] text-white cursor-pointer"
99+
style={{
100+
background:
101+
"linear-gradient(135deg, var(--color-lilac-dark, #6366f1), var(--color-mint-dark, #10b981))",
102+
}}
103+
>
104+
Try it
105+
</button>
106+
</div>
107+
</div>
108+
);
109+
}

0 commit comments

Comments
 (0)