11"use client" ;
22
3- import { useState , useCallback } from "react" ;
43import { useAgent } from "@copilotkit/react-core/v2" ;
54import { TemplateCard } from "./template-card" ;
65
7- /** Submit a message through the CopilotChat textarea so it goes through the full rendering pipeline */
8- function submitChatPrompt ( text : string ) {
9- const textarea = document . querySelector < HTMLTextAreaElement > (
10- '[data-testid="copilot-chat-textarea"]'
11- ) ;
12- if ( ! textarea ) {
13- console . warn ( "submitChatPrompt: CopilotChat textarea not found — template apply message was not sent." ) ;
14- return ;
15- }
16-
17- const setter = Object . getOwnPropertyDescriptor (
18- window . HTMLTextAreaElement . prototype , "value"
19- ) ?. set ;
20- setter ?. call ( textarea , text ) ;
21- textarea . dispatchEvent ( new Event ( "input" , { bubbles : true } ) ) ;
22-
23- setTimeout ( ( ) => {
24- const form = textarea . closest ( "form" ) ;
25- if ( form ) form . requestSubmit ( ) ;
26- } , 150 ) ;
27- }
28-
296interface TemplateLibraryProps {
307 open : boolean ;
318 onClose : ( ) => void ;
@@ -46,41 +23,24 @@ export function TemplateLibrary({ open, onClose }: TemplateLibraryProps) {
4623 const { agent } = useAgent ( ) ;
4724 const templates : Template [ ] = agent . state ?. templates || [ ] ;
4825
49- // Apply flow: show input for new data before sending
50- const [ applyingTemplate , setApplyingTemplate ] = useState < Template | null > ( null ) ;
51- const [ applyData , setApplyData ] = useState ( "" ) ;
52-
5326 const handleApplyClick = ( id : string ) => {
5427 const template = templates . find ( ( t ) => t . id === id ) ;
55- if ( template ) {
56- setApplyingTemplate ( template ) ;
57- setApplyData ( "" ) ;
58- }
59- } ;
60-
61- const handleApplyConfirm = useCallback ( ( ) => {
62- if ( ! applyingTemplate ) return ;
63- const dataDesc = applyData . trim ( ) ;
64- if ( ! dataDesc ) return ;
28+ if ( ! template ) return ;
6529
66- // Set pending_template in agent state so the agent knows which template to apply.
67- // Spread full state to guard against replace-style setState.
30+ // Attach template as a chip in the chat input — user types their prompt naturally
6831 agent . setState ( {
6932 ...agent . state ,
70- pending_template : { id : applyingTemplate . id , name : applyingTemplate . name } ,
33+ pending_template : { id : template . id , name : template . name } ,
7134 } ) ;
72-
73- // Send only the user's data description — no template ID in the message
74- submitChatPrompt ( dataDesc ) ;
75-
76- setApplyingTemplate ( null ) ;
77- setApplyData ( "" ) ;
7835 onClose ( ) ;
79- } , [ agent , applyingTemplate , applyData , onClose ] ) ;
8036
81- const handleApplyCancel = ( ) => {
82- setApplyingTemplate ( null ) ;
83- setApplyData ( "" ) ;
37+ // Focus the chat textarea so user can start typing immediately
38+ setTimeout ( ( ) => {
39+ const textarea = document . querySelector < HTMLTextAreaElement > (
40+ '[data-testid="copilot-chat-textarea"]'
41+ ) ;
42+ textarea ?. focus ( ) ;
43+ } , 100 ) ;
8444 } ;
8545
8646 const handleDelete = ( id : string ) => {
@@ -193,71 +153,6 @@ export function TemplateLibrary({ open, onClose }: TemplateLibraryProps) {
193153 ) }
194154 </ div >
195155
196- { /* Apply data input — slides up from bottom of drawer */ }
197- { applyingTemplate && (
198- < div
199- className = "shrink-0 px-4 pb-4 pt-3"
200- style = { {
201- borderTop : "1px solid var(--color-border-glass, rgba(0,0,0,0.1))" ,
202- animation : "tmpl-slideIn 0.2s ease-out" ,
203- } }
204- >
205- < p
206- className = "text-xs font-semibold mb-1"
207- style = { { color : "var(--text-primary, #1a1a1a)" } }
208- >
209- Apply "{ applyingTemplate . name } "
210- </ p >
211- < p
212- className = "text-[11px] mb-2"
213- style = { { color : "var(--text-tertiary, #999)" } }
214- >
215- Describe the new data you want to populate this template with:
216- </ p >
217- < textarea
218- value = { applyData }
219- onChange = { ( e ) => setApplyData ( e . target . value ) }
220- onKeyDown = { ( e ) => {
221- if ( e . key === "Enter" && ! e . shiftKey ) {
222- e . preventDefault ( ) ;
223- handleApplyConfirm ( ) ;
224- }
225- if ( e . key === "Escape" ) handleApplyCancel ( ) ;
226- } }
227- autoFocus
228- placeholder = 'e.g. "$2,400 web design project for Sarah Chen, due April 15"'
229- rows = { 2 }
230- className = "w-full text-xs px-3 py-2 rounded-lg outline-none resize-none"
231- style = { {
232- background : "var(--color-background-secondary, #f5f5f5)" ,
233- color : "var(--text-primary, #1a1a1a)" ,
234- border : "1px solid var(--color-border-tertiary, rgba(0,0,0,0.1))" ,
235- } }
236- />
237- < div className = "flex gap-2 mt-2" >
238- < button
239- onClick = { handleApplyConfirm }
240- disabled = { ! applyData . trim ( ) }
241- className = "flex-1 text-xs font-medium py-1.5 rounded-lg text-white transition-all duration-150 disabled:opacity-40"
242- style = { {
243- background : "linear-gradient(135deg, var(--color-lilac-dark, #6366f1), var(--color-mint-dark, #10b981))" ,
244- } }
245- >
246- Apply Template
247- </ button >
248- < button
249- onClick = { handleApplyCancel }
250- className = "text-xs px-3 py-1.5 rounded-lg"
251- style = { {
252- border : "1px solid var(--color-border-tertiary, rgba(0,0,0,0.1))" ,
253- color : "var(--text-secondary, #666)" ,
254- } }
255- >
256- Cancel
257- </ button >
258- </ div >
259- </ div >
260- ) }
261156 </ div >
262157 </ >
263158 ) ;
0 commit comments