@@ -135,6 +135,59 @@ export default function DebugWizard() {
135135 setState ( ( prev ) => ( { ...prev , [ field ] : value } ) ) ;
136136 } ;
137137
138+ const handleFileUpload = async ( files : File [ ] ) => {
139+ const maxFiles = limits . maxFiles ?? 100 ;
140+ const maxSizeMB = limits . maxFileSizeMB ?? 1 ;
141+ const maxSizeBytes = maxSizeMB * 1024 * 1024 ;
142+
143+ // Check file count limit
144+ const totalFiles = state . files . length + files . length ;
145+ if ( totalFiles > maxFiles ) {
146+ setError ( `Maximum ${ maxFiles } files allowed. You have ${ state . files . length } and tried to add ${ files . length } .` ) ;
147+ return ;
148+ }
149+
150+ const newFiles : FileInput [ ] = [ ] ;
151+
152+ for ( const file of files ) {
153+ // Check file size
154+ if ( file . size > maxSizeBytes ) {
155+ setError ( `File "${ file . name } " exceeds the ${ maxSizeMB } MB limit.` ) ;
156+ continue ;
157+ }
158+
159+ // Read file content
160+ try {
161+ const content = await file . text ( ) ;
162+ newFiles . push ( {
163+ path : file . name ,
164+ content : content ,
165+ size_bytes : file . size ,
166+ } ) ;
167+ } catch ( err ) {
168+ console . error ( 'Error reading file:' , err ) ;
169+ setError ( `Could not read file "${ file . name } ".` ) ;
170+ }
171+ }
172+
173+ if ( newFiles . length > 0 ) {
174+ setState ( ( prev ) => ( {
175+ ...prev ,
176+ files : [ ...prev . files , ...newFiles ] ,
177+ // Auto-fill file path from first file if not set
178+ file_path : prev . file_path || newFiles [ 0 ] . path ,
179+ } ) ) ;
180+ setError ( null ) ;
181+ }
182+ } ;
183+
184+ const removeFile = ( index : number ) => {
185+ setState ( ( prev ) => ( {
186+ ...prev ,
187+ files : prev . files . filter ( ( _ , i ) => i !== index ) ,
188+ } ) ) ;
189+ } ;
190+
138191 const handleAnalyze = async ( ) => {
139192 setError ( null ) ;
140193
@@ -305,10 +358,115 @@ logger = structlog.get_logger()`,
305358 />
306359 </ div >
307360
308- { /* File Path and Line Number */ }
361+ { /* File Upload Section */ }
362+ < div >
363+ < label className = "block text-sm font-medium text-gray-700 mb-2" >
364+ Upload Files (optional)
365+ < span className = "text-gray-500 font-normal ml-2" >
366+ Max { limits . maxFiles === null ? 'unlimited' : limits . maxFiles } file{ limits . maxFiles !== 1 ? 's' : '' } , { limits . maxFileSizeMB ? `${ limits . maxFileSizeMB } MB each` : 'no size limit' }
367+ </ span >
368+ </ label >
369+ < div
370+ className = { `border-2 border-dashed rounded-lg p-6 text-center transition-colors ${
371+ state . files . length > 0 ? 'border-purple-400 bg-purple-50' : 'border-gray-300 hover:border-purple-400'
372+ } `}
373+ onDragOver = { ( e ) => {
374+ e . preventDefault ( ) ;
375+ e . currentTarget . classList . add ( 'border-purple-500' , 'bg-purple-50' ) ;
376+ } }
377+ onDragLeave = { ( e ) => {
378+ e . currentTarget . classList . remove ( 'border-purple-500' , 'bg-purple-50' ) ;
379+ } }
380+ onDrop = { ( e ) => {
381+ e . preventDefault ( ) ;
382+ e . currentTarget . classList . remove ( 'border-purple-500' , 'bg-purple-50' ) ;
383+ const droppedFiles = Array . from ( e . dataTransfer . files ) ;
384+ handleFileUpload ( droppedFiles ) ;
385+ } }
386+ >
387+ { state . files . length === 0 ? (
388+ < >
389+ < div className = "text-4xl mb-2" > 📁</ div >
390+ < p className = "text-gray-600 mb-2" > Drag & drop source files here </ p >
391+ < p className = "text-sm text-gray-500 mb-3" > or</ p >
392+ < label className = "px-4 py-2 bg-purple-600 text-white rounded-lg cursor-pointer hover:bg-purple-700 inline-block" >
393+ Browse Files
394+ < input
395+ type = "file"
396+ multiple
397+ accept = ".ts,.tsx,.js,.jsx,.py,.java,.go,.rs,.rb,.php,.c,.cpp,.h,.hpp,.cs,.swift,.kt,.scala,.vue,.svelte,.json,.yaml,.yml,.md,.txt"
398+ className = "hidden"
399+ onChange = { ( e ) => {
400+ if ( e . target . files ) {
401+ handleFileUpload ( Array . from ( e . target . files ) ) ;
402+ }
403+ } }
404+ />
405+ </ label >
406+ < p className = "text-xs text-gray-400 mt-3" >
407+ Supports: .ts, .tsx, .js, .jsx, .py, .java, .go, .rs, .rb, .php, .c, .cpp, .cs, .swift, .kt, .scala, .vue, .svelte, .json, .yaml
408+ </ p >
409+ </ >
410+ ) : (
411+ < div className = "text-left" >
412+ < div className = "flex items-center justify-between mb-3" >
413+ < span className = "font-medium text-purple-700" >
414+ { state . files . length } file{ state . files . length !== 1 ? 's' : '' } selected
415+ </ span >
416+ < button
417+ type = "button"
418+ onClick = { ( ) => setState ( prev => ( { ...prev , files : [ ] } ) ) }
419+ className = "text-sm text-red-600 hover:text-red-800"
420+ >
421+ Clear all
422+ </ button >
423+ </ div >
424+ < div className = "space-y-2 max-h-40 overflow-y-auto" >
425+ { state . files . map ( ( file , idx ) => (
426+ < div key = { idx } className = "flex items-center justify-between bg-white rounded p-2 border border-purple-200" >
427+ < div className = "flex items-center gap-2 min-w-0" >
428+ < span className = "text-lg" > 📄</ span >
429+ < span className = "text-sm font-mono truncate" > { file . path } </ span >
430+ < span className = "text-xs text-gray-400" >
431+ ({ Math . round ( file . content . length / 1024 * 10 ) / 10 } KB)
432+ </ span >
433+ </ div >
434+ < button
435+ type = "button"
436+ onClick = { ( ) => removeFile ( idx ) }
437+ className = "text-red-500 hover:text-red-700 ml-2"
438+ >
439+ ✕
440+ </ button >
441+ </ div >
442+ ) ) }
443+ </ div >
444+ < label className = "mt-3 text-sm text-purple-600 hover:text-purple-800 cursor-pointer inline-block" >
445+ + Add more files
446+ < input
447+ type = "file"
448+ multiple
449+ accept = ".ts,.tsx,.js,.jsx,.py,.java,.go,.rs,.rb,.php,.c,.cpp,.h,.hpp,.cs,.swift,.kt,.scala,.vue,.svelte,.json,.yaml,.yml,.md,.txt"
450+ className = "hidden"
451+ onChange = { ( e ) => {
452+ if ( e . target . files ) {
453+ handleFileUpload ( Array . from ( e . target . files ) ) ;
454+ }
455+ } }
456+ />
457+ </ label >
458+ </ div >
459+ ) }
460+ </ div >
461+ </ div >
462+
463+ { /* File Path and Line Number (manual entry fallback) */ }
309464 < div className = "grid grid-cols-1 md:grid-cols-3 gap-4" >
310465 < div className = "md:col-span-2" >
311- < label className = "block text-sm font-medium text-gray-700 mb-2" > File Path</ label >
466+ < label className = "block text-sm font-medium text-gray-700 mb-2" >
467+ File Path
468+ < span className = "text-gray-500 font-normal ml-2" > (or enter manually if not uploading)</ span >
469+ </ label >
312470 < input
313471 type = "text"
314472 value = { state . file_path }
0 commit comments