@@ -5938,6 +5938,7 @@ export function renderUpscalingSection(node, container, modelLists) {
59385938 <option value="model_only" ${ ucfg . mode === "model_only" ? 'selected' : '' } >Model Upscale Only</option>
59395939 <option value="model_then_hires" ${ ucfg . mode === "model_then_hires" ? 'selected' : '' } >Model Upscale \u2192 HiRes Fix</option>
59405940 <option value="seedvr2" ${ ucfg . mode === "seedvr2" ? 'selected' : '' } >SeedVR2 Upscale</option>
5941+ <option value="florence2_hires" ${ ucfg . mode === "florence2_hires" ? 'selected' : '' } >Florence2 Hi-Res Fix</option>
59415942 ` ;
59425943 modeSelect . onchange = ( ) => {
59435944 ucfg . mode = modeSelect . value ;
@@ -5956,9 +5957,10 @@ export function renderUpscalingSection(node, container, modelLists) {
59565957 resizeSelect . onchange = ( ) => { ucfg . resize_method = resizeSelect . value ; node . saveState ( ) ; } ;
59575958 grid . appendChild ( createInputGroup ( "Resize Method" , resizeSelect ) ) ;
59585959
5959- const showHires = ucfg . mode === "hires_only" || ucfg . mode === "model_then_hires" ;
5960+ const showHires = ucfg . mode === "hires_only" || ucfg . mode === "model_then_hires" || ucfg . mode === "florence2_hires" ;
59605961 const showModel = ucfg . mode === "model_only" || ucfg . mode === "model_then_hires" ;
59615962 const showSeedVR2 = ucfg . mode === "seedvr2" ;
5963+ const showFlorence2 = ucfg . mode === "florence2_hires" ;
59625964
59635965 // --- HiRes fields (multi-value: ratios, denoise) ---
59645966 if ( showHires ) {
@@ -6362,6 +6364,126 @@ export function renderUpscalingSection(node, container, modelLists) {
63626364 grid . appendChild ( createInputGroup ( "Cache VAE Model" , vaeCacheCb ) ) ;
63636365 }
63646366
6367+ // --- Florence2 Hi-Res Fix sub-panel ---
6368+ if ( showFlorence2 ) {
6369+ if ( ! ucfg . florence2 ) ucfg . florence2 = createDefaultFlorence2Options ( ) ;
6370+ const f2 = ucfg . florence2 ;
6371+
6372+ // --- Group A: Florence2 detection ---
6373+ const f2ModelSelect = document . createElement ( "select" ) ;
6374+ f2ModelSelect . className = "cb-select" ;
6375+ f2ModelSelect . title = "Florence2 model for segmentation. base/large work for referring_expression_segmentation; ft variants slightly worse." ;
6376+ [
6377+ "microsoft/Florence-2-base" ,
6378+ "microsoft/Florence-2-base-ft" ,
6379+ "microsoft/Florence-2-large" ,
6380+ "microsoft/Florence-2-large-ft" ,
6381+ ] . forEach ( name => {
6382+ const opt = document . createElement ( "option" ) ;
6383+ opt . value = name ; opt . textContent = name ;
6384+ if ( f2 . model === name ) opt . selected = true ;
6385+ f2ModelSelect . appendChild ( opt ) ;
6386+ } ) ;
6387+ f2ModelSelect . onchange = ( ) => { f2 . model = f2ModelSelect . value ; node . saveState ( ) ; } ;
6388+ grid . appendChild ( createInputGroup ( "Florence2 Model" , f2ModelSelect ) ) ;
6389+
6390+ const f2TextInput = document . createElement ( "input" ) ;
6391+ f2TextInput . type = "text" ; f2TextInput . className = "cb-input" ;
6392+ f2TextInput . value = f2 . text_input || "face" ;
6393+ f2TextInput . placeholder = "face, head, hands, eyes..." ;
6394+ f2TextInput . title = "What to detect. Try: face, head, hands, eyes, person, clothing." ;
6395+ f2TextInput . onchange = ( ) => { f2 . text_input = f2TextInput . value ; node . saveState ( ) ; } ;
6396+ grid . appendChild ( createInputGroup ( "Detect What?" , f2TextInput ) ) ;
6397+
6398+ const f2MaskSelInput = document . createElement ( "input" ) ;
6399+ f2MaskSelInput . type = "text" ; f2MaskSelInput . className = "cb-input" ;
6400+ f2MaskSelInput . value = f2 . output_mask_select || "" ;
6401+ f2MaskSelInput . placeholder = '(empty = all)' ;
6402+ f2MaskSelInput . title = "Empty = all detections. '0' = primary/largest. '0,2' = pick specific indices." ;
6403+ f2MaskSelInput . onchange = ( ) => { f2 . output_mask_select = f2MaskSelInput . value ; node . saveState ( ) ; } ;
6404+ grid . appendChild ( createInputGroup ( "Output Mask Select" , f2MaskSelInput ) ) ;
6405+
6406+ // --- Group B: Crop & resize ---
6407+ const f2MpInput = document . createElement ( "input" ) ;
6408+ f2MpInput . type = "number" ; f2MpInput . className = "cb-input" ;
6409+ f2MpInput . value = f2 . target_megapixels ?? 1.0 ;
6410+ f2MpInput . min = 0.25 ; f2MpInput . max = 4.0 ; f2MpInput . step = 0.25 ;
6411+ f2MpInput . title = "Resize cropped region to this many megapixels before the hi-res pass. 1.0 ≈ 1024×1024." ;
6412+ f2MpInput . onchange = ( ) => { f2 . target_megapixels = parseFloat ( f2MpInput . value ) ; node . saveState ( ) ; } ;
6413+ grid . appendChild ( createInputGroup ( "Hi-Res Target (MP)" , f2MpInput ) ) ;
6414+
6415+ const f2PadInput = document . createElement ( "input" ) ;
6416+ f2PadInput . type = "number" ; f2PadInput . className = "cb-input" ;
6417+ f2PadInput . value = f2 . crop_padding ?? 64 ;
6418+ f2PadInput . min = 0 ; f2PadInput . max = 256 ; f2PadInput . step = 8 ;
6419+ f2PadInput . title = "Padding around detected polygon. Higher = more blending context." ;
6420+ f2PadInput . onchange = ( ) => { f2 . crop_padding = parseInt ( f2PadInput . value , 10 ) ; node . saveState ( ) ; } ;
6421+ grid . appendChild ( createInputGroup ( "Crop Padding (px)" , f2PadInput ) ) ;
6422+
6423+ const f2MinCropInput = document . createElement ( "input" ) ;
6424+ f2MinCropInput . type = "number" ; f2MinCropInput . className = "cb-input" ;
6425+ f2MinCropInput . value = f2 . min_crop_resolution ?? 256 ;
6426+ f2MinCropInput . min = 64 ; f2MinCropInput . max = 4096 ; f2MinCropInput . step = 8 ;
6427+ f2MinCropInput . title = "Floor for crop size — prevents tiny inpaints." ;
6428+ f2MinCropInput . onchange = ( ) => { f2 . min_crop_resolution = parseInt ( f2MinCropInput . value , 10 ) ; node . saveState ( ) ; } ;
6429+ grid . appendChild ( createInputGroup ( "Min Crop Res (px)" , f2MinCropInput ) ) ;
6430+
6431+ const f2MaxCropInput = document . createElement ( "input" ) ;
6432+ f2MaxCropInput . type = "number" ; f2MaxCropInput . className = "cb-input" ;
6433+ f2MaxCropInput . value = f2 . max_crop_resolution ?? 1536 ;
6434+ f2MaxCropInput . min = 64 ; f2MaxCropInput . max = 4096 ; f2MaxCropInput . step = 8 ;
6435+ f2MaxCropInput . title = "Ceiling for crop size — prevents OOM on full-frame detections." ;
6436+ f2MaxCropInput . onchange = ( ) => { f2 . max_crop_resolution = parseInt ( f2MaxCropInput . value , 10 ) ; node . saveState ( ) ; } ;
6437+ grid . appendChild ( createInputGroup ( "Max Crop Res (px)" , f2MaxCropInput ) ) ;
6438+
6439+ // --- Group C: Mask shaping ---
6440+ const f2GrowInput = document . createElement ( "input" ) ;
6441+ f2GrowInput . type = "number" ; f2GrowInput . className = "cb-input" ;
6442+ f2GrowInput . value = f2 . grow_expand ?? 32 ;
6443+ f2GrowInput . min = - 64 ; f2GrowInput . max = 256 ; f2GrowInput . step = 1 ;
6444+ f2GrowInput . title = "GrowMask expand. Negative shrinks. Adds pixels to polygon edges." ;
6445+ f2GrowInput . onchange = ( ) => { f2 . grow_expand = parseInt ( f2GrowInput . value , 10 ) ; node . saveState ( ) ; } ;
6446+ grid . appendChild ( createInputGroup ( "Grow Mask (px)" , f2GrowInput ) ) ;
6447+
6448+ [ "left" , "top" , "right" , "bottom" ] . forEach ( side => {
6449+ const inp = document . createElement ( "input" ) ;
6450+ inp . type = "number" ; inp . className = "cb-input" ;
6451+ const key = `feather_${ side } ` ;
6452+ inp . value = f2 [ key ] ?? 128 ;
6453+ inp . min = 0 ; inp . max = 256 ; inp . step = 1 ;
6454+ inp . title = `FeatherMask ${ side } -side alpha falloff in pixels.` ;
6455+ inp . onchange = ( ) => { f2 [ key ] = parseInt ( inp . value , 10 ) ; node . saveState ( ) ; } ;
6456+ grid . appendChild ( createInputGroup ( `Feather ${ side . charAt ( 0 ) . toUpperCase ( ) + side . slice ( 1 ) } (px)` , inp ) ) ;
6457+ } ) ;
6458+
6459+ // --- Group E: Model/LoRA source ---
6460+ const f2SourceSelect = document . createElement ( "select" ) ;
6461+ f2SourceSelect . className = "cb-select" ;
6462+ f2SourceSelect . title = "'From manifest' uses each image's original model/LoRA. 'From this Builder config' uses the same model the upscale session loaded." ;
6463+ [
6464+ [ "from_manifest" , "From manifest (per-image)" ] ,
6465+ [ "from_builder" , "From this Builder config" ] ,
6466+ ] . forEach ( ( [ val , lbl ] ) => {
6467+ const opt = document . createElement ( "option" ) ;
6468+ opt . value = val ; opt . textContent = lbl ;
6469+ if ( f2 . model_source === val ) opt . selected = true ;
6470+ f2SourceSelect . appendChild ( opt ) ;
6471+ } ) ;
6472+ f2SourceSelect . onchange = ( ) => { f2 . model_source = f2SourceSelect . value ; node . saveState ( ) ; } ;
6473+ grid . appendChild ( createInputGroup ( "Model/LoRA Source" , f2SourceSelect ) ) ;
6474+
6475+ // --- Group F: No-detection ---
6476+ const f2NoDetSelect = document . createElement ( "select" ) ;
6477+ f2NoDetSelect . className = "cb-select" ;
6478+ f2NoDetSelect . title = "What to do when Florence2 finds nothing in the image." ;
6479+ const optSkip = document . createElement ( "option" ) ;
6480+ optSkip . value = "skip" ; optSkip . textContent = "Skip + log" ;
6481+ if ( ( f2 . on_no_detection || "skip" ) === "skip" ) optSkip . selected = true ;
6482+ f2NoDetSelect . appendChild ( optSkip ) ;
6483+ f2NoDetSelect . onchange = ( ) => { f2 . on_no_detection = f2NoDetSelect . value ; node . saveState ( ) ; } ;
6484+ grid . appendChild ( createInputGroup ( "If No Detection" , f2NoDetSelect ) ) ;
6485+ }
6486+
63656487 card . appendChild ( grid ) ;
63666488
63676489 // Iteration count for this step
@@ -6372,6 +6494,7 @@ export function renderUpscalingSection(node, container, modelLists) {
63726494 const models = Math . max ( 1 , ( ucfg . upscale_models || [ ] ) . length ) ;
63736495 let combos = 1 ;
63746496 if ( showSeedVR2 ) combos = 1 ; // SeedVR2 = 1 output per input
6497+ else if ( showFlorence2 ) combos = 1 ; // Florence2 = 1 output per input (uses hires_denoise as a single value, not array)
63756498 else if ( showHires ) combos *= ratios * denoises ;
63766499 if ( showModel ) combos *= models ;
63776500 const repeatCount = ucfg . repeat || 1 ;
0 commit comments