Skip to content

Commit bfd3844

Browse files
committed
feat(templates): seed 3 built-in templates on first load
Add Weather, Invoice Card, and Calculator as pre-built templates that populate the template library when no templates exist yet. Each has production-quality HTML using the app's theme variables.
1 parent f54777b commit bfd3844

3 files changed

Lines changed: 265 additions & 0 deletions

File tree

apps/app/src/app/page.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import { useEffect, useState } from "react";
44
import { ExampleLayout } from "@/components/example-layout";
55
import { useGenerativeUIExamples, useExampleSuggestions } from "@/hooks";
6+
import { useSeedTemplates } from "@/hooks/use-seed-templates";
67
import { ExplainerCardsPortal } from "@/components/explainer-cards";
78
import { TemplateLibrary } from "@/components/template-library";
89
import { TemplateChip } from "@/components/template-library/template-chip";
@@ -12,6 +13,7 @@ import { CopilotChat } from "@copilotkit/react-core/v2";
1213
export default function HomePage() {
1314
useGenerativeUIExamples();
1415
useExampleSuggestions();
16+
useSeedTemplates();
1517

1618
const [templateDrawerOpen, setTemplateDrawerOpen] = useState(false);
1719

Lines changed: 234 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,234 @@
1+
/** Pre-built templates that ship with the app */
2+
3+
export interface SeedTemplate {
4+
id: string;
5+
name: string;
6+
description: string;
7+
html: string;
8+
data_description: string;
9+
created_at: string;
10+
version: number;
11+
}
12+
13+
const weatherHtml = `<style>
14+
.weather-card {
15+
font-family: var(--font-sans);
16+
max-width: 380px;
17+
border-radius: var(--border-radius-xl);
18+
background: var(--color-background-secondary);
19+
border: 0.5px solid var(--color-border-tertiary);
20+
padding: 24px;
21+
}
22+
.weather-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 16px; }
23+
.weather-city { font-size: 18px; font-weight: 600; color: var(--color-text-primary); }
24+
.weather-date { font-size: 12px; color: var(--color-text-tertiary); margin-top: 2px; }
25+
.weather-badge {
26+
font-size: 11px; font-weight: 500; padding: 3px 10px; border-radius: 999px;
27+
background: var(--color-background-info); color: var(--color-text-info);
28+
}
29+
.weather-temp { font-size: 48px; font-weight: 700; color: var(--color-text-primary); line-height: 1; }
30+
.weather-desc { font-size: 14px; color: var(--color-text-secondary); margin-top: 4px; }
31+
.weather-details {
32+
display: grid; grid-template-columns: repeat(3, 1fr); gap: 12px;
33+
margin-top: 20px; padding-top: 16px;
34+
border-top: 0.5px solid var(--color-border-tertiary);
35+
}
36+
.weather-detail-label { font-size: 11px; color: var(--color-text-tertiary); }
37+
.weather-detail-value { font-size: 14px; font-weight: 600; color: var(--color-text-primary); margin-top: 2px; }
38+
</style>
39+
<div class="weather-card">
40+
<div class="weather-header">
41+
<div>
42+
<div class="weather-city">New York, NY</div>
43+
<div class="weather-date">Tuesday, March 25, 2026</div>
44+
</div>
45+
<span class="weather-badge">Partly Cloudy</span>
46+
</div>
47+
<div class="weather-temp">72°F</div>
48+
<div class="weather-desc">Partly cloudy with a gentle breeze from the southwest</div>
49+
<div class="weather-details">
50+
<div>
51+
<div class="weather-detail-label">Humidity</div>
52+
<div class="weather-detail-value">54%</div>
53+
</div>
54+
<div>
55+
<div class="weather-detail-label">Wind</div>
56+
<div class="weather-detail-value">8 mph SW</div>
57+
</div>
58+
<div>
59+
<div class="weather-detail-label">UV Index</div>
60+
<div class="weather-detail-value">5 (Moderate)</div>
61+
</div>
62+
</div>
63+
</div>`;
64+
65+
const invoiceHtml = `<style>
66+
.invoice-card {
67+
font-family: var(--font-sans);
68+
max-width: 420px;
69+
border-radius: var(--border-radius-xl);
70+
background: var(--color-background-secondary);
71+
border: 0.5px solid var(--color-border-tertiary);
72+
padding: 24px;
73+
}
74+
.invoice-header { display: flex; justify-content: space-between; align-items: flex-start; margin-bottom: 8px; }
75+
.invoice-title { font-size: 18px; font-weight: 600; color: var(--color-text-primary); }
76+
.invoice-subtitle { font-size: 12px; color: var(--color-text-tertiary); margin-top: 2px; }
77+
.invoice-badge {
78+
font-size: 11px; font-weight: 500; padding: 3px 10px; border-radius: 999px;
79+
background: var(--color-background-warning); color: var(--color-text-warning);
80+
}
81+
.invoice-amount { font-size: 36px; font-weight: 700; color: var(--color-text-primary); margin: 12px 0 4px; }
82+
.invoice-for { font-size: 13px; color: var(--color-text-secondary); }
83+
.invoice-grid {
84+
display: grid; grid-template-columns: 1fr 1fr; gap: 12px;
85+
margin-top: 16px; padding-top: 16px;
86+
border-top: 0.5px solid var(--color-border-tertiary);
87+
}
88+
.invoice-label { font-size: 11px; color: var(--color-text-tertiary); }
89+
.invoice-value { font-size: 14px; font-weight: 500; color: var(--color-text-primary); margin-top: 2px; }
90+
.invoice-actions { display: flex; gap: 8px; margin-top: 20px; }
91+
.invoice-actions button {
92+
flex: none; font-size: 13px; padding: 8px 18px;
93+
border-radius: var(--border-radius-md);
94+
}
95+
</style>
96+
<div class="invoice-card">
97+
<div class="invoice-header">
98+
<div>
99+
<div class="invoice-title">Monthly invoice</div>
100+
<div class="invoice-subtitle">Starter card for a recurring client billing cycle</div>
101+
</div>
102+
<span class="invoice-badge">Draft</span>
103+
</div>
104+
<div class="invoice-amount">$2,500</div>
105+
<div class="invoice-for">For product design retainer and monthly support</div>
106+
<div class="invoice-grid">
107+
<div>
108+
<div class="invoice-label">Client</div>
109+
<div class="invoice-value">Northwind Labs</div>
110+
</div>
111+
<div>
112+
<div class="invoice-label">Billing month</div>
113+
<div class="invoice-value">March 2026</div>
114+
</div>
115+
<div>
116+
<div class="invoice-label">Invoice number</div>
117+
<div class="invoice-value">INV-3201</div>
118+
</div>
119+
<div>
120+
<div class="invoice-label">Due date</div>
121+
<div class="invoice-value">Apr 5, 2026</div>
122+
</div>
123+
</div>
124+
<div class="invoice-actions">
125+
<button onclick="sendPrompt('Send this invoice')">Send invoice</button>
126+
<button onclick="sendPrompt('Expand to full invoice')">Expand to full invoice ↗</button>
127+
</div>
128+
</div>`;
129+
130+
const calculatorHtml = `<style>
131+
.calc {
132+
font-family: var(--font-sans);
133+
max-width: 320px;
134+
border-radius: var(--border-radius-xl);
135+
background: var(--color-background-secondary);
136+
border: 0.5px solid var(--color-border-tertiary);
137+
overflow: hidden;
138+
}
139+
.calc-display {
140+
padding: 20px 24px 12px;
141+
text-align: right;
142+
}
143+
.calc-expression { font-size: 13px; color: var(--color-text-tertiary); min-height: 18px; }
144+
.calc-result { font-size: 36px; font-weight: 700; color: var(--color-text-primary); line-height: 1.2; }
145+
.calc-grid {
146+
display: grid; grid-template-columns: repeat(4, 1fr); gap: 1px;
147+
background: var(--color-border-tertiary);
148+
border-top: 0.5px solid var(--color-border-tertiary);
149+
}
150+
.calc-btn {
151+
padding: 16px; font-size: 16px; font-weight: 500;
152+
border: none; border-radius: 0; cursor: pointer;
153+
background: var(--color-background-primary);
154+
color: var(--color-text-primary);
155+
transition: background 0.1s;
156+
}
157+
.calc-btn:hover { background: var(--color-background-tertiary); }
158+
.calc-btn.op { color: var(--color-text-info); font-weight: 600; }
159+
.calc-btn.eq {
160+
background: var(--color-background-info); color: var(--color-text-info); font-weight: 600;
161+
}
162+
.calc-btn.eq:hover { opacity: 0.85; }
163+
.calc-btn.wide { grid-column: span 2; }
164+
</style>
165+
<div class="calc">
166+
<div class="calc-display">
167+
<div class="calc-expression" id="expr"></div>
168+
<div class="calc-result" id="result">0</div>
169+
</div>
170+
<div class="calc-grid">
171+
<button class="calc-btn" onclick="clearCalc()">C</button>
172+
<button class="calc-btn op" onclick="inputOp('(')">(</button>
173+
<button class="calc-btn op" onclick="inputOp(')')">)</button>
174+
<button class="calc-btn op" onclick="inputOp('/')">÷</button>
175+
<button class="calc-btn" onclick="inputNum('7')">7</button>
176+
<button class="calc-btn" onclick="inputNum('8')">8</button>
177+
<button class="calc-btn" onclick="inputNum('9')">9</button>
178+
<button class="calc-btn op" onclick="inputOp('*')">×</button>
179+
<button class="calc-btn" onclick="inputNum('4')">4</button>
180+
<button class="calc-btn" onclick="inputNum('5')">5</button>
181+
<button class="calc-btn" onclick="inputNum('6')">6</button>
182+
<button class="calc-btn op" onclick="inputOp('-')">−</button>
183+
<button class="calc-btn" onclick="inputNum('1')">1</button>
184+
<button class="calc-btn" onclick="inputNum('2')">2</button>
185+
<button class="calc-btn" onclick="inputNum('3')">3</button>
186+
<button class="calc-btn op" onclick="inputOp('+')">+</button>
187+
<button class="calc-btn wide" onclick="inputNum('0')">0</button>
188+
<button class="calc-btn" onclick="inputNum('.')">.</button>
189+
<button class="calc-btn eq" onclick="calc()">=</button>
190+
</div>
191+
</div>
192+
<script>
193+
var expression = '';
194+
function inputNum(n) { expression += n; update(); }
195+
function inputOp(o) { expression += o; update(); }
196+
function clearCalc() { expression = ''; document.getElementById('expr').textContent = ''; document.getElementById('result').textContent = '0'; }
197+
function update() { document.getElementById('expr').textContent = expression; }
198+
function calc() {
199+
try {
200+
var r = Function('"use strict"; return (' + expression + ')')();
201+
document.getElementById('result').textContent = Number.isFinite(r) ? parseFloat(r.toFixed(8)).toString() : 'Error';
202+
} catch(e) { document.getElementById('result').textContent = 'Error'; }
203+
}
204+
</script>`;
205+
206+
export const SEED_TEMPLATES: SeedTemplate[] = [
207+
{
208+
id: "seed-weather-001",
209+
name: "Weather",
210+
description: "Current weather conditions card with temperature, humidity, wind, and UV index",
211+
html: weatherHtml,
212+
data_description: "City name, date, temperature, condition, humidity, wind speed/direction, UV index",
213+
created_at: "2026-01-01T00:00:00.000Z",
214+
version: 1,
215+
},
216+
{
217+
id: "seed-invoice-001",
218+
name: "Invoice Card",
219+
description: "Compact invoice card with amount, client info, and action buttons",
220+
html: invoiceHtml,
221+
data_description: "Title, amount, description, client name, billing month, invoice number, due date",
222+
created_at: "2026-01-01T00:00:01.000Z",
223+
version: 1,
224+
},
225+
{
226+
id: "seed-calculator-001",
227+
name: "Calculator",
228+
description: "Interactive calculator with basic arithmetic operations",
229+
html: calculatorHtml,
230+
data_description: "N/A — interactive widget, no data substitution needed",
231+
created_at: "2026-01-01T00:00:02.000Z",
232+
version: 1,
233+
},
234+
];
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
"use client";
2+
3+
import { useEffect, useRef } from "react";
4+
import { useAgent } from "@copilotkit/react-core/v2";
5+
import { SEED_TEMPLATES } from "@/components/template-library/seed-templates";
6+
7+
/**
8+
* Seeds the agent state with built-in templates on first load
9+
* if no templates exist yet.
10+
*/
11+
export function useSeedTemplates() {
12+
const { agent } = useAgent();
13+
const seeded = useRef(false);
14+
15+
useEffect(() => {
16+
if (seeded.current) return;
17+
const existing = agent.state?.templates;
18+
// Only seed if templates array is empty or absent
19+
if (existing && existing.length > 0) {
20+
seeded.current = true;
21+
return;
22+
}
23+
seeded.current = true;
24+
agent.setState({
25+
...agent.state,
26+
templates: [...SEED_TEMPLATES],
27+
});
28+
}, [agent]);
29+
}

0 commit comments

Comments
 (0)