11"use client" ;
22
3- import { useEffect } from "react" ;
3+ import { useEffect , useState , useCallback } from "react" ;
44import { ExampleLayout } from "@/components/example-layout" ;
55import { useGenerativeUIExamples , useExampleSuggestions } from "@/hooks" ;
66import { ExplainerCardsPortal } from "@/components/explainer-cards" ;
7+ import { TemplateLibrary } from "@/components/template-library" ;
78
89import { CopilotChat } from "@copilotkit/react-core/v2" ;
10+ import { useAgent } from "@copilotkit/react-core/v2" ;
911
1012export default function HomePage ( ) {
1113 useGenerativeUIExamples ( ) ;
1214 useExampleSuggestions ( ) ;
1315
14- // Widget bridge: handle openLink from widget iframes
16+ const { agent } = useAgent ( ) ;
17+ const [ templateDrawerOpen , setTemplateDrawerOpen ] = useState ( false ) ;
18+
19+ // Save a template directly to agent state — no chat round-trip
20+ const saveTemplate = useCallback ( ( data : {
21+ name : string ;
22+ title : string ;
23+ description : string ;
24+ html : string ;
25+ } ) => {
26+ const templates = agent . state ?. templates || [ ] ;
27+ const newTemplate = {
28+ id : crypto . randomUUID ( ) ,
29+ name : data . name || data . title || "Untitled Template" ,
30+ description : data . description || data . title || "" ,
31+ html : data . html ,
32+ data_description : "" ,
33+ created_at : new Date ( ) . toISOString ( ) ,
34+ version : 1 ,
35+ } ;
36+ agent . setState ( { templates : [ ...templates , newTemplate ] } ) ;
37+ } , [ agent ] ) ;
38+
39+ // Send a prompt to the CopilotChat by finding its textarea and submitting
40+ const sendPrompt = useCallback ( ( text : string ) => {
41+ const input = document . querySelector < HTMLTextAreaElement > (
42+ '[class*="copilot"] textarea, [data-copilotkit] textarea'
43+ ) ;
44+ if ( input ) {
45+ const setter = Object . getOwnPropertyDescriptor (
46+ window . HTMLTextAreaElement . prototype ,
47+ "value"
48+ ) ?. set ;
49+ setter ?. call ( input , text ) ;
50+ input . dispatchEvent ( new Event ( "input" , { bubbles : true } ) ) ;
51+ setTimeout ( ( ) => {
52+ const form = input . closest ( "form" ) ;
53+ if ( form ) {
54+ form . dispatchEvent ( new Event ( "submit" , { bubbles : true , cancelable : true } ) ) ;
55+ }
56+ } , 50 ) ;
57+ }
58+ } , [ ] ) ;
59+
60+ // Widget bridge: handle messages from widget iframes
1561 useEffect ( ( ) => {
1662 const handler = ( e : MessageEvent ) => {
1763 if ( e . data ?. type === "open-link" && typeof e . data . url === "string" ) {
1864 window . open ( e . data . url , "_blank" , "noopener,noreferrer" ) ;
1965 }
66+ // Handle save-as-template from WidgetRenderer — save directly to state
67+ if ( e . data ?. type === "save-as-template" ) {
68+ saveTemplate ( e . data ) ;
69+ }
2070 } ;
2171 window . addEventListener ( "message" , handler ) ;
2272 return ( ) => window . removeEventListener ( "message" , handler ) ;
23- } , [ ] ) ;
73+ } , [ saveTemplate ] ) ;
2474
2575 return (
2676 < >
@@ -58,19 +108,38 @@ export default function HomePage() {
58108 < span className = "font-normal" style = { { color : "var(--text-secondary)" } } > — powered by CopilotKit</ span >
59109 </ p >
60110 </ div >
61- < a
62- href = "https://github.com/CopilotKit/OpenGenerativeUI"
63- target = "_blank"
64- rel = "noopener noreferrer"
65- className = "inline-flex items-center px-5 py-2 rounded-full text-sm font-semibold text-white no-underline whitespace-nowrap transition-all duration-150 hover:-translate-y-px"
66- style = { {
67- background : "linear-gradient(135deg, var(--color-lilac-dark), var(--color-mint-dark))" ,
68- boxShadow : "0 1px 4px rgba(149,153,204,0.3)" ,
69- fontFamily : "var(--font-family)" ,
70- } }
71- >
72- Get started
73- </ a >
111+ < div className = "flex items-center gap-2" >
112+ { /* Template Library toggle */ }
113+ < button
114+ onClick = { ( ) => setTemplateDrawerOpen ( true ) }
115+ 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"
116+ style = { {
117+ color : "var(--text-secondary)" ,
118+ border : "1px solid var(--color-border-glass, rgba(0,0,0,0.1))" ,
119+ background : "var(--surface-primary, rgba(255,255,255,0.6))" ,
120+ fontFamily : "var(--font-family)" ,
121+ } }
122+ title = "Open Template Library"
123+ >
124+ < svg width = "15" height = "15" viewBox = "0 0 24 24" fill = "none" stroke = "currentColor" strokeWidth = "2" strokeLinecap = "round" strokeLinejoin = "round" >
125+ < path d = "m19 21-7-4-7 4V5a2 2 0 0 1 2-2h10a2 2 0 0 1 2 2v16z" />
126+ </ svg >
127+ Templates
128+ </ button >
129+ < a
130+ href = "https://github.com/CopilotKit/OpenGenerativeUI"
131+ target = "_blank"
132+ rel = "noopener noreferrer"
133+ className = "inline-flex items-center px-5 py-2 rounded-full text-sm font-semibold text-white no-underline whitespace-nowrap transition-all duration-150 hover:-translate-y-px"
134+ style = { {
135+ background : "linear-gradient(135deg, var(--color-lilac-dark), var(--color-mint-dark))" ,
136+ boxShadow : "0 1px 4px rgba(149,153,204,0.3)" ,
137+ fontFamily : "var(--font-family)" ,
138+ } }
139+ >
140+ Get started
141+ </ a >
142+ </ div >
74143 </ div >
75144 </ div >
76145
@@ -84,6 +153,13 @@ export default function HomePage() {
84153 < ExplainerCardsPortal />
85154 </ div >
86155 </ div >
156+
157+ { /* Template Library Drawer */ }
158+ < TemplateLibrary
159+ open = { templateDrawerOpen }
160+ onClose = { ( ) => setTemplateDrawerOpen ( false ) }
161+ onSendPrompt = { sendPrompt }
162+ />
87163 </ >
88164 ) ;
89165}
0 commit comments