99 return ;
1010 }
1111
12- const { Styles, Icon, SettingsModal, StatusBar, ChatMessage, LogsModal, CodePreview, HistoryModal } = window . VC . Components ;
12+ const { Styles, Icon, SettingsModal, StatusBar, ChatMessage, LogsModal, CodePreview, HistoryModal, VibeStarters } = window . VC . Components ;
1313 const Utils = window . VC . Utils ;
1414
1515 // --- DEFAULT TEMPLATE FOR VIRTUAL MODE ---
@@ -147,7 +147,7 @@ button:active { transform: translateY(1px); }`,
147147 apiUrl : 'http://localhost:1234/v1' ,
148148 model : 'local-model' ,
149149 mode : 'auto' ,
150- temperature : null , // Default to null (Use Model Default)
150+ temperature : null ,
151151 } ) ;
152152
153153 const [ setupDone , setSetupDone ] = useState ( false ) ;
@@ -165,8 +165,12 @@ button:active { transform: translateY(1px); }`,
165165 const [ showLogs , setShowLogs ] = useState ( false ) ;
166166
167167 const [ logs , setLogs ] = useState ( [ ] ) ;
168- const [ previewLogs , setPreviewLogs ] = useState ( [ ] ) ; // Separate logs for the Preview Console
168+ const [ previewLogs , setPreviewLogs ] = useState ( [ ] ) ;
169169
170+ // Auto-Fix State
171+ const [ autoFixCount , setAutoFixCount ] = useState ( 0 ) ;
172+ const MAX_AUTO_RETRIES = 3 ;
173+
170174 // Session Logging
171175 const [ sessionLogText , setSessionLogText ] = useState ( '' ) ;
172176 const [ sessionLogFileName , setSessionLogFileName ] = useState ( '' ) ;
@@ -262,6 +266,7 @@ button:active { transform: translateY(1px); }`,
262266
263267 const abortControllerRef = useRef ( null ) ;
264268 const activeAssistantMsgIdRef = useRef ( null ) ;
269+ const autoFixTimeoutRef = useRef ( null ) ;
265270
266271 const filesRef = useRef ( files ) ;
267272 const dirHandleRef = useRef ( dirHandle ) ;
@@ -290,13 +295,32 @@ button:active { transform: translateY(1px); }`,
290295 setShowScrollButton ( ! isNearBottom ) ;
291296 } ;
292297
293- // Preview Event Handling
298+ // Preview Event Handling & AUTO-FIX TRIGGER
294299 useEffect ( ( ) => {
295300 const handler = ( e ) => {
296301 if ( e . data ?. type === 'iframe-error' ) {
297- console . error ( "Preview Error:" , e . data . message ) ;
298- setRuntimeError ( e . data . message ) ;
299- appendSessionLog ( 'preview.iframe_error' , { message : e . data . message } ) . catch ( ( ) => { } ) ;
302+ const errMsg = e . data . message ;
303+ console . error ( "Preview Error:" , errMsg ) ;
304+ setRuntimeError ( errMsg ) ;
305+ appendSessionLog ( 'preview.iframe_error' , { message : errMsg } ) . catch ( ( ) => { } ) ;
306+
307+ // AUTO-FIX LOGIC
308+ // Only trigger if idle, not already fixing too much, and it's a real error
309+ if ( autoFixTimeoutRef . current ) clearTimeout ( autoFixTimeoutRef . current ) ;
310+ autoFixTimeoutRef . current = setTimeout ( ( ) => {
311+ setAppStatus ( prevStatus => {
312+ if ( prevStatus === 'idle' && autoFixCount < MAX_AUTO_RETRIES ) {
313+ log ( 'info' , `Auto-fixing runtime error (Attempt ${ autoFixCount + 1 } /${ MAX_AUTO_RETRIES } )` ) ;
314+ setAutoFixCount ( c => c + 1 ) ;
315+ // Trigger the fix
316+ handleSend ( `I detected a runtime error in the preview:\n"${ errMsg } "\n\nPlease analyze the code and apply a fix. Do not rewrite the entire application if a small patch works.` ) ;
317+ } else if ( autoFixCount >= MAX_AUTO_RETRIES ) {
318+ log ( 'warn' , 'Max auto-fix retries reached. Stopping.' ) ;
319+ setStatusMsg ( 'Automatic fixes paused. Please check manually.' ) ;
320+ }
321+ return prevStatus ;
322+ } ) ;
323+ } , 1000 ) ; // Small debounce to let things settle
300324 }
301325 if ( e . data ?. type === 'iframe-point' ) {
302326 setPointEvents ( prev => {
@@ -312,7 +336,7 @@ button:active { transform: translateY(1px); }`,
312336 } ;
313337 window . addEventListener ( 'message' , handler ) ;
314338 return ( ) => window . removeEventListener ( 'message' , handler ) ;
315- } , [ appendSessionLog ] ) ;
339+ } , [ appendSessionLog , autoFixCount ] ) ; // depend on autoFixCount to know when to stop
316340
317341 // Auto-Save
318342 const saveTimerRef = useRef ( null ) ;
@@ -444,11 +468,18 @@ NO PYTHON. NO MARKDOWN FENCES.`;
444468 return false ;
445469 } ;
446470
447- const handleSend = async ( ) => {
471+ const handleSend = async ( overrideInput ) => {
448472 if ( appStatus !== 'idle' ) { handleStop ( ) ; return ; }
449- if ( ( ! input . trim ( ) && ! attachments . length ) ) return ;
473+
474+ const txt = overrideInput || input ;
475+ // If manually sending, reset auto-fix count
476+ if ( ! overrideInput ) {
477+ setAutoFixCount ( 0 ) ;
478+ }
479+
480+ if ( ( ! txt . trim ( ) && ! attachments . length ) ) return ;
450481
451- const userText = input ;
482+ const userText = txt ;
452483 const userMsgId = makeId ( ) ;
453484 const assistantMsgId = makeId ( ) ;
454485 activeAssistantMsgIdRef . current = assistantMsgId ;
@@ -458,7 +489,7 @@ NO PYTHON. NO MARKDOWN FENCES.`;
458489 } catch { }
459490
460491 if ( Object . keys ( files ) . length > 0 ) {
461- setHistory ( prev => [ ...prev , { timestamp : Date . now ( ) , files : JSON . parse ( JSON . stringify ( files ) ) , prompt : input || "Upload" } ] ) ;
492+ setHistory ( prev => [ ...prev , { timestamp : Date . now ( ) , files : JSON . parse ( JSON . stringify ( files ) ) , prompt : txt || "Upload" } ] ) ;
462493 setCurrentVersionIndex ( history . length ) ;
463494 }
464495
@@ -477,13 +508,17 @@ NO PYTHON. NO MARKDOWN FENCES.`;
477508 let promptSuffix = assetNotices . length > 0 ? `\n\nAVAILABLE ASSETS:\n${ assetNotices . map ( p => `- ${ p } ` ) . join ( '\n' ) } ` : "" ;
478509
479510 if ( attachments . length > 0 ) {
480- userContent = [ { type : "text" , text : ( input || "Analyze images." ) + promptSuffix } ] ;
511+ userContent = [ { type : "text" , text : ( txt || "Analyze images." ) + promptSuffix } ] ;
481512 attachments . forEach ( att => userContent . push ( { type : "image_url" , image_url : { url : att . data } } ) ) ;
482513 } else {
483- userContent = ( input || '' ) + promptSuffix ;
514+ userContent = ( txt || '' ) + promptSuffix ;
484515 }
485516
486- const displayUserText = ( input || '[Images Uploaded]' ) + ( assetNotices . length ? `\n[+ Added ${ assetNotices . length } assets]` : '' ) ;
517+ const displayUserText = ( txt || '[Images Uploaded]' ) + ( assetNotices . length ? `\n[+ Added ${ assetNotices . length } assets]` : '' ) ;
518+
519+ // Don't show "I detected a runtime error..." as a User bubble if it's auto-fix,
520+ // or do show it so they know what's happening?
521+ // User vibe: Transparency is good. "System detected error" is fine.
487522
488523 setMessages ( prev => ( [
489524 ...prev ,
@@ -512,7 +547,7 @@ NO PYTHON. NO MARKDOWN FENCES.`;
512547 } ) . join ( '\n\n' ) ;
513548
514549 let contextString = `PROJECT CONTEXT (CURRENT FILES):\n${ contextFiles } \n\nUSER REQUEST: ${ userText } ` ;
515- if ( runtimeError ) contextString += `\n\n!!! DETECTED RUNTIME ERROR IN PREVIEW !!!\nError: ${ runtimeError } \nPLEASE FIX THIS ERROR.` ;
550+ if ( runtimeError && ! overrideInput ) contextString += `\n\n!!! DETECTED RUNTIME ERROR IN PREVIEW !!!\nError: ${ runtimeError } \nPLEASE FIX THIS ERROR.` ;
516551 if ( pointEvents . length ) contextString += `\n\nPOINT & VIBE SELECTIONS:\n` + pointEvents . map ( ( p , idx ) => `#${ idx + 1 } : tag=<${ p . tag } > text="${ ( p . text || '' ) . slice ( 0 , 50 ) } "` ) . join ( '\n' ) ;
517552
518553 await appendSessionLog ( 'request.context_files' , contextFilesList ) ;
@@ -740,6 +775,12 @@ NO PYTHON. NO MARKDOWN FENCES.`;
740775 </ div >
741776 < div className ="flex-1 overflow-y-auto p-4 space-y-4 custom-scrollbar " ref =${ chatContainerRef } onScroll =${ handleChatScroll } >
742777 ${ messages . map ( m => html `< ${ ChatMessage } key =${ m . id } msg=${ m } /> ` ) }
778+ ${ messages . length === 1 && html `
779+ < div className ="flex flex-col items-center justify-center py-6 ">
780+ < p className ="text-gray-500 text-sm mb-2 "> No idea? Try a vibe starter:</ p >
781+ < ${ VibeStarters } onSelect =${ ( prompt ) => handleSend ( prompt ) } />
782+ </ div >
783+ ` }
743784 < div ref =${ msgsEndRef } > </ div >
744785 </ div >
745786 ${ showScrollButton && html `< button onClick =${ ( ) => msgsEndRef . current ?. scrollIntoView ( { behavior : 'smooth' } ) } className ="absolute bottom-[220px] right-6 p-2 bg-gray-800 border border-gray-700 text-white rounded-full shadow-lg shadow-black/50 hover:bg-gray-700 transition z-20"> < svg width ="20 " height ="20 " viewBox ="0 0 24 24 " fill ="none " stroke ="currentColor " stroke-width ="2 "> < polyline points ="6 9 12 15 18 9 "> </ polyline > </ svg > </ button > ` }
@@ -752,7 +793,7 @@ NO PYTHON. NO MARKDOWN FENCES.`;
752793 < input type ="file " id ="file-upload " multiple accept ="image/* " className ="hidden " onChange =${ handleFileSelect } / >
753794 < label for ="file-upload " className ="p-2 text-gray-500 hover:text-blue-400 cursor-pointer transition " title ="Attach Image "> < ${ Icon } name ="Image" /> </ label >
754795 < textarea value =${ input } onInput =${ e => setInput ( e . target . value ) } onKeyDown=${ e => e . key === 'Enter' && ! e . shiftKey && ( e . preventDefault ( ) , handleSend ( ) ) } className="flex-1 bg-transparent text-sm outline-none resize-none text-gray-200 max-h-32 py-2" rows=${ 1 } style=${ { minHeight : '24px' } } placeholder="Ask to change something..." />
755- < button onClick =${ handleSend } disabled =${ ( ! input . trim ( ) && ! attachments . length ) && appStatus === 'idle' } className=${ `p-2 rounded-lg shadow-lg transition flex-shrink-0 ${ appStatus !== 'idle' ? 'bg-red-600 hover:bg-red-500 text-white shadow-red-900/20' : 'bg-blue-600 hover:bg-blue-500 text-white shadow-blue-600/20 disabled:opacity-50 disabled:cursor-not-allowed' } ` } > < ${ Icon } name =${ appStatus !== 'idle' ? 'Stop' : 'Send' } /> </ button >
796+ < button onClick =${ ( ) => handleSend ( ) } disabled =${ ( ! input . trim ( ) && ! attachments . length ) && appStatus === 'idle' } className=${ `p-2 rounded-lg shadow-lg transition flex-shrink-0 ${ appStatus !== 'idle' ? 'bg-red-600 hover:bg-red-500 text-white shadow-red-900/20' : 'bg-blue-600 hover:bg-blue-500 text-white shadow-blue-600/20 disabled:opacity-50 disabled:cursor-not-allowed' } ` } > < ${ Icon } name =${ appStatus !== 'idle' ? 'Stop' : 'Send' } /> </ button >
756797 </ div >
757798 </ div >
758799 </ div >
0 commit comments