Skip to content

Commit 917d72b

Browse files
committed
fix(templates): seed delete bug, backend seed lookup, and delete button visibility
- Hide delete button on seed templates (they reappeared after deletion because client-side merge re-added them on every render) - handleDelete now filters from agentTemplates only, not the merged list - Backend apply_template and list_templates now include SEED_TEMPLATES as a fallback so users can apply seeds by name in chat without clicking the UI button (fixes chat-only flow for seed templates) - Seed HTML loaded from frontend source at module init (single source of truth)
1 parent b24a7a9 commit 917d72b

4 files changed

Lines changed: 93 additions & 17 deletions

File tree

apps/agent/src/templates.py

Lines changed: 75 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,70 @@ class UITemplate(TypedDict, total=False):
1818
component_data: Optional[dict[str, Any]]
1919

2020

21+
# Built-in seed templates — must stay in sync with apps/app/src/components/template-library/seed-templates.ts
22+
# Only id, name, description, html, and data_description are needed for apply_template lookups.
23+
SEED_TEMPLATES: list[UITemplate] = [
24+
{
25+
"id": "seed-weather-001",
26+
"name": "Weather",
27+
"description": "Current weather conditions card with temperature, humidity, wind, and UV index",
28+
"html": "", # Populated at module load from _SEED_HTML below
29+
"data_description": "City name, date, temperature, condition, humidity, wind speed/direction, UV index",
30+
"version": 1,
31+
},
32+
{
33+
"id": "seed-invoice-001",
34+
"name": "Invoice Card",
35+
"description": "Compact invoice card with amount, client info, and action buttons",
36+
"html": "",
37+
"data_description": "Title, amount, description, client name, billing month, invoice number, due date",
38+
"version": 1,
39+
},
40+
{
41+
"id": "seed-dashboard-001",
42+
"name": "Dashboard",
43+
"description": "KPI dashboard with metrics cards and bar chart for quarterly performance",
44+
"html": "",
45+
"data_description": "Title, subtitle, KPI labels/values/changes, monthly bar chart data, legend items",
46+
"version": 1,
47+
},
48+
]
49+
50+
# Load seed HTML from the frontend source so there's a single source of truth.
51+
# If the file isn't available (e.g. in a standalone agent deploy), seeds will
52+
# still be discoverable by name but with empty HTML — the agent can regenerate.
53+
def _load_seed_html() -> None:
54+
from pathlib import Path
55+
56+
seed_file = Path(__file__).resolve().parents[2] / "app" / "src" / "components" / "template-library" / "seed-templates.ts"
57+
if not seed_file.exists():
58+
return
59+
text = seed_file.read_text()
60+
# Map TS variable names to seed IDs
61+
mapping = {
62+
"weatherHtml": "seed-weather-001",
63+
"invoiceHtml": "seed-invoice-001",
64+
"dashboardHtml": "seed-dashboard-001",
65+
}
66+
for var_name, seed_id in mapping.items():
67+
# Extract template literal content between first ` and last `
68+
marker = f"const {var_name} = `"
69+
start = text.find(marker)
70+
if start == -1:
71+
continue
72+
start += len(marker)
73+
end = text.find("`;", start)
74+
if end == -1:
75+
continue
76+
html = text[start:end]
77+
for seed in SEED_TEMPLATES:
78+
if seed["id"] == seed_id:
79+
seed["html"] = html
80+
break
81+
82+
_load_seed_html()
83+
84+
2185
@tool
2286
def save_template(
2387
name: str,
@@ -63,9 +127,12 @@ def save_template(
63127
@tool
64128
def list_templates(runtime: ToolRuntime):
65129
"""
66-
List all saved UI templates. Returns template summaries (id, name, description, data_description).
130+
List all saved UI templates, including built-in seed templates.
131+
Returns template summaries (id, name, description, data_description).
67132
"""
68-
templates = runtime.state.get("templates", [])
133+
state_templates = runtime.state.get("templates", [])
134+
state_ids = {t["id"] for t in state_templates}
135+
templates = [*state_templates, *(s for s in SEED_TEMPLATES if s["id"] not in state_ids)]
69136
return [
70137
{
71138
"id": t["id"],
@@ -88,11 +155,16 @@ def apply_template(runtime: ToolRuntime, name: str = "", template_id: str = ""):
88155
frontend when the user picks a template from the library). If pending_template
89156
is present, it takes priority over name/template_id arguments.
90157
158+
Also searches built-in seed templates, so users can apply them by name in chat
159+
even if the frontend hasn't pushed them into agent state yet.
160+
91161
Args:
92162
name: The name of the template to apply (fallback if no pending_template)
93163
template_id: The ID of the template to apply (fallback if no pending_template)
94164
"""
95-
templates = runtime.state.get("templates", [])
165+
state_templates = runtime.state.get("templates", [])
166+
state_ids = {t["id"] for t in state_templates}
167+
templates = [*state_templates, *(s for s in SEED_TEMPLATES if s["id"] not in state_ids)]
96168

97169
# Check pending_template from frontend first — this is the most reliable source
98170
pending = runtime.state.get("pending_template")

apps/app/src/components/template-library/index.tsx

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { useEffect } from "react";
44
import { useAgent } from "@copilotkit/react-core/v2";
55
import { TemplateCard } from "./template-card";
6-
import { SEED_TEMPLATES } from "./seed-templates";
6+
import { SEED_TEMPLATES, SEED_IDS } from "./seed-templates";
77

88
interface TemplateLibraryProps {
99
open: boolean;
@@ -75,7 +75,7 @@ export function TemplateLibrary({ open, onClose }: TemplateLibraryProps) {
7575
const handleDelete = (id: string) => {
7676
agent.setState({
7777
...agent.state,
78-
templates: templates.filter((t) => t.id !== id),
78+
templates: agentTemplates.filter((t) => t.id !== id),
7979
});
8080
};
8181

@@ -175,7 +175,7 @@ export function TemplateLibrary({ open, onClose }: TemplateLibraryProps) {
175175
dataDescription={t.data_description}
176176
version={t.version}
177177
onApply={handleApplyClick}
178-
onDelete={handleDelete}
178+
onDelete={SEED_IDS.has(t.id) ? undefined : handleDelete}
179179
/>
180180
))}
181181
</div>

apps/app/src/components/template-library/seed-templates.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -198,6 +198,8 @@ const dashboardHtml = `<style>
198198
</div>
199199
</div>`;
200200

201+
export const SEED_IDS = new Set(["seed-weather-001", "seed-invoice-001", "seed-dashboard-001"]);
202+
201203
export const SEED_TEMPLATES: SeedTemplate[] = [
202204
{
203205
id: "seed-weather-001",

apps/app/src/components/template-library/template-card.tsx

Lines changed: 13 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ interface TemplateCardProps {
1818
dataDescription: string;
1919
version: number;
2020
onApply: (id: string) => void;
21-
onDelete: (id: string) => void;
21+
onDelete?: (id: string) => void;
2222
}
2323

2424
/** Mini bar chart preview rendered as inline SVG */
@@ -219,16 +219,18 @@ body {
219219
>
220220
Apply
221221
</button>
222-
<button
223-
onClick={() => onDelete(id)}
224-
className="text-xs px-3 py-1.5 rounded-lg transition-colors duration-150"
225-
style={{
226-
border: "1px solid var(--color-border-tertiary, rgba(0,0,0,0.1))",
227-
color: "var(--color-text-danger, #A32D2D)",
228-
}}
229-
>
230-
Delete
231-
</button>
222+
{onDelete && (
223+
<button
224+
onClick={() => onDelete(id)}
225+
className="text-xs px-3 py-1.5 rounded-lg transition-colors duration-150"
226+
style={{
227+
border: "1px solid var(--color-border-tertiary, rgba(0,0,0,0.1))",
228+
color: "var(--color-text-danger, #A32D2D)",
229+
}}
230+
>
231+
Delete
232+
</button>
233+
)}
232234
</div>
233235
</div>
234236
);

0 commit comments

Comments
 (0)