@@ -907,6 +907,72 @@ <h3 data-i18n="continue_project">Continue Project</h3>
907907 </ div >
908908</ div >
909909
910+ <!-- RESTART MODAL -->
911+ < div class ="modal-backdrop " id ="modal-restart ">
912+ < div class ="modal " style ="max-width:620px ">
913+ < h3 > Restart Project</ h3 >
914+ < div class ="form-group ">
915+ < label > Research Idea</ label >
916+ < textarea id ="restart-idea " rows ="4 " style ="width:100% "> </ textarea >
917+ </ div >
918+ < div class ="form-group ">
919+ < label > Instructions < span style ="font-weight:400;color:var(--text-muted) "> (optional)</ span > </ label >
920+ < textarea id ="restart-comment " rows ="3 " style ="width:100% "
921+ placeholder ="e.g. Focus on a different baseline, change experiment setup... "> </ textarea >
922+ </ div >
923+ < div class ="form-row ">
924+ < div class ="form-group " style ="flex:2 ">
925+ < label > Venue</ label >
926+ < select id ="restart-venue " onchange ="restartVenueChanged(this) "> </ select >
927+ </ div >
928+ < div class ="form-group " style ="flex:1 ">
929+ < label > Max iterations</ label >
930+ < input type ="number " id ="restart-max-iter " value ="3 " min ="1 " max ="3 " style ="width:80px " />
931+ </ div >
932+ </ div >
933+ < input type ="hidden " id ="restart-venue-format " />
934+ < input type ="hidden " id ="restart-venue-pages " />
935+ < div class ="form-group ">
936+ < label > Model</ label >
937+ < div style ="display:flex;gap:8px;flex-wrap:wrap " id ="restart-model-options ">
938+ < label class ="model-option "> < input type ="radio " name ="restart-model " value ="claude-sonnet-4-6 " checked /> < span class ="model-chip "> Sonnet 4.6</ span > </ label >
939+ < label class ="model-option restart-model-admin " style ="display:none "> < input type ="radio " name ="restart-model " value ="claude-opus-4-6 " /> < span class ="model-chip "> Opus 4.6</ span > </ label >
940+ < label class ="model-option restart-model-admin " style ="display:none "> < input type ="radio " name ="restart-model " value ="claude-haiku-4-5 " /> < span class ="model-chip "> Haiku 4.5</ span > </ label >
941+ < label class ="model-option restart-model-admin " style ="display:none "> < input type ="radio " name ="restart-model " value ="gemini " /> < span class ="model-chip "> Gemini</ span > </ label >
942+ </ div >
943+ </ div >
944+ < div class ="tg-section " style ="margin-top:12px ">
945+ < div class ="tg-section-title ">
946+ < svg width ="18 " height ="18 " viewBox ="0 0 24 24 " fill ="none " style ="flex-shrink:0 ">
947+ < circle cx ="12 " cy ="12 " r ="12 " fill ="#29B6F6 "/>
948+ < path d ="M5.5 11.5l10-4.5-1.5 9-3-2.5-2 2v-3l5-4.5-6.5 3.5-2-1z " fill ="white "/>
949+ </ svg >
950+ < span > Telegram Notifications</ span >
951+ </ div >
952+ < div class ="form-row " style ="margin-top:8px ">
953+ < div class ="form-group " style ="margin-bottom:0 ">
954+ < label > Bot Token</ label >
955+ < input type ="text " id ="restart-tg-token " placeholder ="123456789:ABCdef... " autocomplete ="off " />
956+ </ div >
957+ < div class ="form-group " style ="margin-bottom:0 ">
958+ < label > Chat ID</ label >
959+ < input type ="text " id ="restart-tg-chatid " placeholder ="1234567890 " />
960+ </ div >
961+ </ div >
962+ </ div >
963+ < div id ="restart-deep-research-section " style ="margin-top:16px;display:none ">
964+ < label style ="display:flex;align-items:center;gap:8px;cursor:pointer ">
965+ < input type ="checkbox " id ="restart-keep-dr " checked />
966+ < span > Keep existing Deep Research report < span style ="color:var(--text-muted);font-weight:400 "> (uncheck to redo)</ span > </ span >
967+ </ label >
968+ </ div >
969+ < div class ="modal-footer ">
970+ < button type ="button " class ="btn-secondary " onclick ="closeModal('modal-restart') "> Cancel</ button >
971+ < button class ="btn-primary " id ="restart-btn " onclick ="submitRestart() "> Restart</ button >
972+ </ div >
973+ </ div >
974+ </ div >
975+
910976<!-- ADMIN EDIT MODAL -->
911977< div class ="modal-backdrop " id ="modal-admin-edit ">
912978 < div class ="modal " style ="max-width:620px ">
@@ -1903,7 +1969,7 @@ <h3 data-i18n="edit_project">Edit Project</h3>
19031969 html += `<button class="btn-secondary" onclick="stopProject('${ p . id } ')">${ t ( 'stop' ) } </button>` ;
19041970 }
19051971 if ( p . status === 'stopped' || p . status === 'failed' ) {
1906- html += `<button class="btn-primary" onclick="startProject ('${ p . id } ')">${ t ( 'start' ) } </button>` ;
1972+ html += `<button class="btn-primary" onclick="openRestartModal ('${ p . id } ')">Restart </button>` ;
19071973 }
19081974 if ( p . status === 'done' ) {
19091975 html += `<button class="btn-primary" onclick="openContinueModal('${ p . id } ')">${ t ( 'continue_action' ) } </button>` ;
@@ -2259,6 +2325,107 @@ <h3 data-i18n="edit_project">Edit Project</h3>
22592325 }
22602326}
22612327
2328+ // ══════════════════════════════════════════════════════
2329+ // Restart Modal
2330+ // ══════════════════════════════════════════════════════
2331+ let restartProjectId = null ;
2332+
2333+ async function openRestartModal ( id ) {
2334+ restartProjectId = id ;
2335+ // Fetch project detail to pre-fill fields
2336+ const res = await fetch ( `/api/projects/${ id } ` ) ;
2337+ if ( ! res . ok ) { alert ( 'Failed to load project details' ) ; return ; }
2338+ const p = await res . json ( ) ;
2339+
2340+ document . getElementById ( 'restart-idea' ) . value = p . idea || '' ;
2341+ document . getElementById ( 'restart-comment' ) . value = '' ;
2342+ document . getElementById ( 'restart-max-iter' ) . value = p . max_iterations || 3 ;
2343+ document . getElementById ( 'restart-tg-token' ) . value = p . telegram_token || '' ;
2344+ document . getElementById ( 'restart-tg-chatid' ) . value = p . telegram_chat_id || '' ;
2345+
2346+ // Populate venue select (reuse venues from hero form)
2347+ const venueSelect = document . getElementById ( 'restart-venue' ) ;
2348+ const heroSelect = document . getElementById ( 'hero-venue-select' ) ;
2349+ venueSelect . innerHTML = heroSelect . innerHTML ;
2350+ // Set current venue
2351+ if ( p . venue ) {
2352+ for ( const opt of venueSelect . options ) {
2353+ if ( opt . value === p . venue || opt . textContent === p . venue ) {
2354+ opt . selected = true ;
2355+ break ;
2356+ }
2357+ }
2358+ }
2359+ // Set hidden venue_format and venue_pages from current project
2360+ document . getElementById ( 'restart-venue-format' ) . value = p . venue_format || 'neurips' ;
2361+ document . getElementById ( 'restart-venue-pages' ) . value = p . venue_pages || 9 ;
2362+
2363+ // Set model radio
2364+ const modelMap = { 'claude-sonnet-4-6' : 'claude-sonnet-4-6' , 'claude-opus-4-6' : 'claude-opus-4-6' ,
2365+ 'claude-haiku-4-5' : 'claude-haiku-4-5' , 'gemini' : 'gemini' , 'sonnet' : 'claude-sonnet-4-6' ,
2366+ 'opus' : 'claude-opus-4-6' , 'haiku' : 'claude-haiku-4-5' } ;
2367+ const modelVal = modelMap [ p . model ] || 'claude-sonnet-4-6' ;
2368+ document . querySelectorAll ( 'input[name="restart-model"]' ) . forEach ( r => {
2369+ r . checked = ( r . value === modelVal ) ;
2370+ } ) ;
2371+ // Show admin model options if user is admin
2372+ if ( currentUser && currentUser . is_admin ) {
2373+ document . querySelectorAll ( '.restart-model-admin' ) . forEach ( el => el . style . display = '' ) ;
2374+ }
2375+
2376+ // Deep research checkbox
2377+ const drSection = document . getElementById ( 'restart-deep-research-section' ) ;
2378+ if ( p . has_deep_research ) {
2379+ drSection . style . display = '' ;
2380+ document . getElementById ( 'restart-keep-dr' ) . checked = true ;
2381+ } else {
2382+ drSection . style . display = 'none' ;
2383+ }
2384+
2385+ document . getElementById ( 'modal-restart' ) . classList . add ( 'open' ) ;
2386+ }
2387+
2388+ function restartVenueChanged ( sel ) {
2389+ const opt = sel . options [ sel . selectedIndex ] ;
2390+ if ( opt && opt . dataset . format ) {
2391+ document . getElementById ( 'restart-venue-format' ) . value = opt . dataset . format ;
2392+ document . getElementById ( 'restart-venue-pages' ) . value = opt . dataset . pages || 9 ;
2393+ }
2394+ }
2395+
2396+ async function submitRestart ( ) {
2397+ const btn = document . getElementById ( 'restart-btn' ) ;
2398+ btn . disabled = true ;
2399+ try {
2400+ const body = {
2401+ idea : document . getElementById ( 'restart-idea' ) . value . trim ( ) ,
2402+ venue : document . getElementById ( 'restart-venue' ) . value ,
2403+ venue_format : document . getElementById ( 'restart-venue-format' ) . value ,
2404+ venue_pages : parseInt ( document . getElementById ( 'restart-venue-pages' ) . value ) || 9 ,
2405+ max_iterations : Math . max ( 1 , Math . min ( 3 , parseInt ( document . getElementById ( 'restart-max-iter' ) . value ) || 3 ) ) ,
2406+ model : ( [ ...document . querySelectorAll ( 'input[name="restart-model"]' ) ] . find ( r => r . checked ) || { } ) . value || 'claude-sonnet-4-6' ,
2407+ telegram_token : document . getElementById ( 'restart-tg-token' ) . value . trim ( ) ,
2408+ telegram_chat_id : document . getElementById ( 'restart-tg-chatid' ) . value . trim ( ) ,
2409+ comment : document . getElementById ( 'restart-comment' ) . value . trim ( ) ,
2410+ redo_deep_research : ! document . getElementById ( 'restart-keep-dr' ) . checked ,
2411+ } ;
2412+ const res = await fetch ( `/api/projects/${ restartProjectId } /restart` , {
2413+ method : 'POST' ,
2414+ headers : { 'Content-Type' : 'application/json' } ,
2415+ body : JSON . stringify ( body ) ,
2416+ } ) ;
2417+ if ( ! res . ok ) {
2418+ const e = await res . json ( ) . catch ( ( ) => ( { } ) ) ;
2419+ alert ( e . detail || 'Failed to restart project.' ) ;
2420+ return ;
2421+ }
2422+ closeModal ( 'modal-restart' ) ;
2423+ loadDetail ( restartProjectId ) ;
2424+ } finally {
2425+ btn . disabled = false ;
2426+ }
2427+ }
2428+
22622429// ══════════════════════════════════════════════════════
22632430// Utils
22642431// ══════════════════════════════════════════════════════
0 commit comments