11"use client" ;
22
3- import { useState } from "react" ;
3+ import { useState , useRef } from "react" ;
44import { useRouter } from "next/navigation" ;
55import { useMutation , useQuery , useQueryClient } from "@tanstack/react-query" ;
66import { useTRPC } from "@/lib/trpc/react" ;
77import { useGatewayHealth } from "@/hooks/use-gateway-health" ;
88import { Button } from "@/components/ui/button" ;
9- import { MessageSquarePlus } from "lucide-react" ;
9+ import { MessageSquarePlus , Paperclip , ChevronDown , Check , X } from "lucide-react" ;
1010import { Textarea } from "@/components/ui/textarea" ;
1111
1212export default function NewSessionPage ( ) {
1313 const [ agentId , setAgentId ] = useState ( "" ) ;
1414 const [ message , setMessage ] = useState ( "" ) ;
15+
16+ // Real State Wiring for Pre-Session Configuration Form Updates
17+ const [ model , setModel ] = useState ( "" ) ;
18+ const [ workflow , setWorkflow ] = useState ( "" ) ;
19+ const [ skill , setSkill ] = useState ( "" ) ;
20+ const [ thinkingLevel , setThinkingLevel ] = useState ( "" ) ;
21+ const [ attachments , setAttachments ] = useState < File [ ] > ( [ ] ) ;
22+
23+ const fileInputRef = useRef < HTMLInputElement > ( null ) ;
24+
1525 const router = useRouter ( ) ;
1626 const trpc = useTRPC ( ) ;
1727 const queryClient = useQueryClient ( ) ;
@@ -27,52 +37,163 @@ export default function NewSessionPage() {
2737 onSuccess : ( _data , variables ) => {
2838 queryClient . invalidateQueries ( { queryKey : trpc . sessions . list . queryKey ( ) } ) ;
2939 setMessage ( "" ) ;
40+ setAttachments ( [ ] ) ;
3041 router . push ( `/sessions/${ encodeURIComponent ( variables . sessionKey ) } ` ) ;
3142 } ,
3243 } )
3344 ) ;
3445
35- const handleCreate = ( ) => {
46+ const handleFileChange = ( e : React . ChangeEvent < HTMLInputElement > ) => {
47+ if ( e . target . files ) {
48+ setAttachments ( ( prev ) => [ ...prev , ...Array . from ( e . target . files ! ) ] ) ;
49+ }
50+ } ;
51+
52+ const handleCreate = async ( ) => {
3653 if ( ! message . trim ( ) || ! agentId ) return ;
3754 const sessionKey = `agent:${ agentId } :${ Date . now ( ) } ` ;
3855 const idempotencyKey = crypto . randomUUID ( ) ;
56+
57+ // Process files
58+ const filePayloads = await Promise . all (
59+ attachments . map ( async ( f ) => {
60+ return new Promise < { name : string ; type : string ; size : number ; base64 : string } > ( ( resolve ) => {
61+ const reader = new FileReader ( ) ;
62+ reader . onload = ( e ) => {
63+ resolve ( {
64+ name : f . name ,
65+ type : f . type ,
66+ size : f . size ,
67+ base64 : e . target ?. result as string , // Extracts Data-URI string format
68+ } ) ;
69+ } ;
70+ reader . readAsDataURL ( f ) ;
71+ } ) ;
72+ } )
73+ ) ;
74+
3975 sendMutation . mutate ( {
4076 sessionKey,
4177 message : message . trim ( ) ,
4278 idempotencyKey,
79+ model : model || undefined ,
80+ thinkingLevel : thinkingLevel || undefined ,
81+ workflow : workflow || undefined ,
82+ skills : skill ? [ skill ] : undefined ,
83+ attachments : filePayloads . length > 0 ? filePayloads : undefined ,
4384 } ) ;
4485 } ;
4586
4687 return (
47- < div className = "flex h-full flex-col items-center justify-center p-6 bg-zinc-950" >
48- < div className = "mx-auto flex w-full max-w-2xl flex-col items-center text-center" >
49- < div className = "mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-zinc-900 ring-1 ring-zinc-800" >
88+ < div className = "flex h-full flex-col items-center justify-center p-6 bg-zinc-950 overflow-y-auto " >
89+ < div className = "mx-auto flex w-full max-w-3xl flex-col items-center text-center my-auto " >
90+ < div className = "mb-6 flex h-16 w-16 items-center justify-center rounded-full bg-zinc-900 ring-1 ring-zinc-800 shadow-sm " >
5091 < MessageSquarePlus className = "h-8 w-8 text-zinc-400" />
5192 </ div >
5293 < h1 className = "mb-2 text-2xl font-semibold tracking-tight text-zinc-50" >
5394 Start a New Session
5495 </ h1 >
5596 < p className = "mb-8 max-w-md text-sm text-zinc-400" >
56- Select an agent and send your first message to begin a new conversation.
97+ Configure your agent and send your first message to begin a new conversation.
5798 </ p >
5899
59- < div className = "w-full rounded-xl border border-zinc-800 bg-zinc-900/50 p-4 shadow-sm" >
60- < div className = "mb-4" >
61- < select
62- value = { agentId }
63- onChange = { ( e ) => setAgentId ( e . target . value ) }
64- className = "w-full rounded-lg border border-zinc-700 bg-zinc-900 px-4 py-2.5 text-sm text-zinc-200 outline-none focus:border-zinc-500 focus:ring-1 focus:ring-zinc-500"
65- >
66- < option value = "" > Select an agent...</ option >
67- { agents . map ( ( a ) => (
68- < option key = { a . id } value = { a . id } >
69- { a . emoji ? `${ a . emoji } ` : "" } { a . name ?? a . id }
70- </ option >
71- ) ) }
72- </ select >
100+ < div className = "w-full rounded-2xl border border-zinc-800 bg-zinc-900/40 p-5 shadow-lg backdrop-blur-xl" >
101+
102+ { /* Configuration Grid */ }
103+ < div className = "mb-5 grid grid-cols-2 gap-4" >
104+
105+ { /* Agent Select */ }
106+ < div className = "flex flex-col gap-1.5 text-left" >
107+ < label className = "text-xs font-medium text-zinc-400 px-1" > Agent</ label >
108+ < div className = "relative" >
109+ < select
110+ value = { agentId }
111+ onChange = { ( e ) => setAgentId ( e . target . value ) }
112+ className = "w-full appearance-none rounded-xl border border-zinc-700/80 bg-zinc-900/50 px-4 py-2.5 text-sm text-zinc-200 outline-none transition-all focus:border-indigo-500/50 focus:ring-1 focus:ring-indigo-500/50 hover:bg-zinc-800/50"
113+ disabled = { sendMutation . isPending }
114+ >
115+ < option value = "" > Select an agent...</ option >
116+ { agents . map ( ( a ) => (
117+ < option key = { a . id } value = { a . id } >
118+ { a . emoji ? `${ a . emoji } ` : "" } { a . name ?? a . id }
119+ </ option >
120+ ) ) }
121+ </ select >
122+ < ChevronDown className = "absolute right-3 top-3 h-4 w-4 text-zinc-500 pointer-events-none" />
123+ </ div >
124+ </ div >
125+
126+ { /* Model Select */ }
127+ < div className = "flex flex-col gap-1.5 text-left" >
128+ < label className = "text-xs font-medium text-zinc-400 px-1" > Model Override</ label >
129+ < div className = "relative" >
130+ < select
131+ value = { model }
132+ onChange = { ( e ) => setModel ( e . target . value ) }
133+ className = "w-full appearance-none rounded-xl border border-zinc-700/80 bg-zinc-900/50 px-4 py-2.5 text-sm text-zinc-200 outline-none transition-all focus:border-indigo-500/50 focus:ring-1 focus:ring-indigo-500/50 hover:bg-zinc-800/50"
134+ disabled = { sendMutation . isPending }
135+ >
136+ < option value = "" > Agent Default</ option >
137+ < option value = "sonnet-4.6" > Claude 4.6 Sonnet</ option >
138+ < option value = "opus-4.6" > Claude 4.6 Opus</ option >
139+ < option value = "haiku-4.5" > Claude 4.5 Haiku</ option >
140+ < option value = "gpt-5.2" > GPT-5.2</ option >
141+ < option value = "gpt-5.3-codex" > GPT-5.3 Codex</ option >
142+ </ select >
143+ < ChevronDown className = "absolute right-3 top-3 h-4 w-4 text-zinc-500 pointer-events-none" />
144+ </ div >
145+ </ div >
146+
147+ { /* Workflow Select */ }
148+ < div className = "flex flex-col gap-1.5 text-left" >
149+ < label className = "text-xs font-medium text-zinc-400 px-1" > Workflow Template</ label >
150+ < div className = "relative" >
151+ < select
152+ value = { workflow }
153+ onChange = { ( e ) => setWorkflow ( e . target . value ) }
154+ className = "w-full appearance-none rounded-xl border border-zinc-700/80 bg-zinc-900/50 px-4 py-2.5 text-sm text-zinc-200 outline-none transition-all focus:border-indigo-500/50 focus:ring-1 focus:ring-indigo-500/50 hover:bg-zinc-800/50"
155+ disabled = { sendMutation . isPending }
156+ >
157+ < option value = "" > None applied</ option >
158+ < option value = "scaffold" > Scaffold New Project (/scaffold)</ option >
159+ < option value = "pr-review" > Pull Request Review (/pr-review)</ option >
160+ < option value = "atomic-commit" > Deploy Atomic Commit (/atomic-commit)</ option >
161+ </ select >
162+ < ChevronDown className = "absolute right-3 top-3 h-4 w-4 text-zinc-500 pointer-events-none" />
163+ </ div >
164+ </ div >
165+
166+ { /* Skills Map Select */ }
167+ < div className = "flex flex-col gap-1.5 text-left" >
168+ < label className = "text-xs font-medium text-zinc-400 px-1" > Skill Profile</ label >
169+ < div className = "relative" >
170+ < select
171+ value = { skill }
172+ onChange = { ( e ) => setSkill ( e . target . value ) }
173+ className = "w-full appearance-none rounded-xl border border-zinc-700/80 bg-zinc-900/50 px-4 py-2.5 text-sm text-zinc-200 outline-none transition-all focus:border-indigo-500/50 focus:ring-1 focus:ring-indigo-500/50 hover:bg-zinc-800/50"
174+ disabled = { sendMutation . isPending }
175+ >
176+ < option value = "" > Default toolkit</ option >
177+ < option value = "web-tavily" > Deep Web Search (Tavily)</ option >
178+ < option value = "mcp-local" > Local Filesystem (MCP)</ option >
179+ < option value = "all" > Full Tool Belt</ option >
180+ </ select >
181+ < ChevronDown className = "absolute right-3 top-3 h-4 w-4 text-zinc-500 pointer-events-none" />
182+ </ div >
183+ </ div >
184+
73185 </ div >
74186
75- < div className = "relative" >
187+ < input
188+ type = "file"
189+ multiple
190+ className = "hidden"
191+ ref = { fileInputRef }
192+ onChange = { handleFileChange }
193+ />
194+
195+ { /* Main Input Area */ }
196+ < div className = "relative rounded-xl border border-zinc-700 bg-zinc-900/80 shadow-inner focus-within:border-zinc-500 focus-within:ring-1 focus-within:ring-zinc-500 transition-all flex flex-col" >
76197 < Textarea
77198 value = { message }
78199 onChange = { ( e : React . ChangeEvent < HTMLTextAreaElement > ) => setMessage ( e . target . value ) }
@@ -83,20 +204,76 @@ export default function NewSessionPage() {
83204 }
84205 } }
85206 placeholder = "What can I help you with?"
86- className = "min-h-[120px] resize-none border-zinc-700 bg-zinc-900 pb-12 pt-4 text-base placeholder:text-zinc-500 focus-visible:ring-1 focus-visible:ring-zinc-600 rounded-lg text-zinc-200"
207+ className = "min-h-[140px] resize-none border-0 bg-transparent px-4 py-4 text-sm placeholder:text-zinc-500 focus-visible:ring-0 rounded-t-xl text-zinc-200"
208+ disabled = { sendMutation . isPending }
87209 />
88- < div className = "absolute bottom-3 right-3 flex items-center justify-end" >
210+
211+ { attachments . length > 0 && (
212+ < div className = "flex flex-wrap gap-2 px-3 pb-3" >
213+ { attachments . map ( ( file , i ) => (
214+ < div key = { i } className = "flex items-center gap-1.5 bg-zinc-800/80 border border-zinc-700 rounded-md px-2 py-1 text-xs text-zinc-300" >
215+ < Paperclip className = "h-3 w-3 text-zinc-500" />
216+ < span className = "truncate max-w-[150px]" > { file . name } </ span >
217+ < button
218+ onClick = { ( ) => setAttachments ( a => a . filter ( ( _ , idx ) => idx !== i ) ) }
219+ className = "ml-1 text-zinc-500 hover:text-red-400 transition-colors"
220+ aria-label = "Remove attachment"
221+ >
222+ < X className = "h-3 w-3" />
223+ </ button >
224+ </ div >
225+ ) ) }
226+ </ div >
227+ ) }
228+
229+ { /* Input Action Bar */ }
230+ < div className = "flex items-center justify-between border-t border-zinc-800/60 bg-zinc-900/40 px-3 py-2 rounded-b-xl" >
231+ < div className = "flex items-center gap-2" >
232+
233+ { /* File Attachment Button */ }
234+ < button
235+ onClick = { ( ) => fileInputRef . current ?. click ( ) }
236+ className = "flex h-8 items-center justify-center rounded-lg px-2.5 text-zinc-400 hover:bg-zinc-800 hover:text-zinc-200 transition-colors"
237+ aria-label = "Attach file"
238+ title = "Attach folders or files"
239+ disabled = { sendMutation . isPending }
240+ >
241+ < Paperclip className = "h-4 w-4" />
242+ </ button >
243+
244+ < div className = "h-4 w-px bg-zinc-800 mx-1" > </ div >
245+
246+ { /* Reasoning Controls */ }
247+ < div className = "flex bg-zinc-900/50 p-0.5 rounded-lg border border-zinc-800" >
248+ { ( [ "" , "low" , "medium" , "high" ] as const ) . map ( ( level ) => (
249+ < button
250+ key = { level }
251+ onClick = { ( ) => setThinkingLevel ( level ) }
252+ disabled = { sendMutation . isPending }
253+ className = { `flex h-7 items-center px-3 rounded-md text-xs font-medium transition-all ${ thinkingLevel === level
254+ ? "bg-indigo-500/20 text-indigo-300 ring-1 ring-indigo-500/30"
255+ : "text-zinc-500 hover:text-zinc-300 hover:bg-zinc-800/50"
256+ } `}
257+ >
258+ { level === "" ? "Default" : level . charAt ( 0 ) . toUpperCase ( ) + level . slice ( 1 ) }
259+ </ button >
260+ ) ) }
261+ </ div >
262+ </ div >
263+
264+ { /* Submit Action */ }
89265 < Button
90266 onClick = { handleCreate }
91267 disabled = { ! message . trim ( ) || ! agentId || ! canSend || sendMutation . isPending }
92- className = "h-8 shrink-0 rounded-md bg-zinc-100 px-4 text-xs font-medium text-zinc-900 transition-colors hover:bg-zinc-300 disabled:opacity-50"
268+ className = "h-8 shrink-0 rounded-lg bg-zinc-100 px-5 text-xs font-semibold text-zinc-900 transition-all hover:bg-zinc-300 hover:scale-[1.02] active:scale-[0.98] disabled:opacity-50 disabled:hover:scale-100 shadow-[0_0_15px_rgba(255,255,255,0.1)] "
93269 >
94- { sendMutation . isPending ? "Starting..." : "Start Session" }
270+ { sendMutation . isPending ? "Starting session ..." : "Start Session" }
95271 </ Button >
96272 </ div >
97273 </ div >
274+
98275 { ! canSend && (
99- < p className = "mt-3 text-xs text-red-400/80 text-left" >
276+ < p className = "mt-3 text-xs text-red-400/80 text-left px-1 " >
100277 Gateway is disconnected or chat.send is unavailable.
101278 </ p >
102279 ) }
0 commit comments