@@ -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
2286def save_template (
2387 name : str ,
@@ -63,9 +127,12 @@ def save_template(
63127@tool
64128def 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" )
0 commit comments