@@ -526,7 +526,7 @@ function _selectedServeTarget(panel) {
526526 env : server ?. env || '' ,
527527 port : host ? ( server ?. port || _getPort ( host ) || '' ) : '' ,
528528 venv,
529- platform : server ?. platform || _envState . platform || '' ,
529+ platform : host ? ( server ?. platform || '' ) : ( _envState . hostPlatform || '' ) ,
530530 label,
531531 } ;
532532}
@@ -657,6 +657,12 @@ function _selectedGgufSizeGb(model, relPath) {
657657 return bytes / ( 1024 ** 3 ) ;
658658}
659659
660+ function _projectorGgufFiles ( model ) {
661+ return _ggufFilesForModel ( model )
662+ . filter ( f => ( f . role || '' ) === 'projector' || / ( ^ | \/ ) m m p r o j [ ^ / ] * \. g g u f $ / i. test ( f . rel_path || f . name || '' ) )
663+ . sort ( ( a , b ) => String ( a . rel_path || a . name || '' ) . localeCompare ( String ( b . rel_path || b . name || '' ) ) ) ;
664+ }
665+
660666function _ggufFileLabel ( file ) {
661667 const base = ( file . name || file . rel_path || '' ) . split ( '/' ) . pop ( ) ;
662668 const size = _formatGgufSize ( file . size_bytes ) ;
@@ -1197,8 +1203,8 @@ function _rerenderCachedModels() {
11971203 panelHtml += `<div class="hwfit-serve-warn" style="margin:0 0 8px;padding:6px 10px;border-radius:5px;font-size:11px;background:color-mix(in srgb, var(--color-warning, #f0ad4e) 14%, transparent);border:1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 40%, transparent);color:var(--color-warning, #f0ad4e);display:flex;gap:6px;align-items:flex-start;line-height:1.4;"><span aria-hidden="true">⚠</span><span>${ _warnText } </span></div>` ;
11981204 }
11991205 panelHtml += `<div class="hwfit-serve-preset-row">${ _slotsHtml } </div>` ;
1200- // Row 1: Engine + Server + Env
1201- panelHtml += `<div class="hwfit-serve-row">` ;
1206+ panelHtml += `<div class="hwfit-serve-vision-warn" style="display:none;margin:0 0 8px;padding:6px 10px;border-radius:5px;font-size:11px;background:color-mix(in srgb, var(--color-warning, #f0ad4e) 14%, transparent);border:1px solid color-mix(in srgb, var(--color-warning, #f0ad4e) 40%, transparent);color:var(--color-warning, #f0ad4e);gap:6px;align-items:flex-start;line-height:1.4;"><span aria-hidden="true">⚠</span><span>Vision is enabled, but no mmproj GGUF projector was found in the cached model scan. Download an mmproj-*.gguf for this model, then refresh the cached model list before launching.</span></div>` ;
1207+ // Row 1: Engine + Server + Env panelHtml += `<div class="hwfit-serve-row">`;
12021208 const backendOpts = _backendChoices . map ( ( [ v , l ] ) => `<option value="${ v } "${ defaultBackend === v ?' selected' :'' } >${ l } </option>` ) . join ( '' ) ;
12031209 // Custom Backend picker — native <select> can't host SVG inside
12041210 // options, so we render a button + menu that show the backend logo
@@ -1542,11 +1548,11 @@ function _rerenderCachedModels() {
15421548 : m . is_local_dir && m . path
15431549 ? `$({ find ${ _ldir } -name '*-00001-of-*.gguf' 2>/dev/null | sort; find ${ _ldir } -name '*.gguf' 2>/dev/null | sort; } | head -1)`
15441550 : `$({ find ${ dir } -name '*-00001-of-*.gguf' 2>/dev/null | sort; find ${ dir } -name '*.gguf' 2>/dev/null | sort; } | head -1)` ;
1545- // Vision: auto-find the mmproj (CLIP/projector ) file in the same dir .
1546- // Resolved at runtime so the toggle just works if an mmproj-*.gguf is
1547- // present (downloaded alongside the model). Empty if none → cmd omits it .
1548- const _vsearchdir = ( m . is_local_dir && m . path ) ? _ldir : dir ;
1549- f . _mmproj_path = `$(find ${ _vsearchdir } -iname 'mmproj*.gguf' 2>/dev/null | sort | head -1)` ;
1551+ // Vision: use the scanned projector (CLIP/mmproj ) file when present .
1552+ // Keeping this as a printf path avoids generating a command substitution
1553+ // that the backend serve-command validator must reject as unsafe .
1554+ const selectedProjector = _projectorGgufFiles ( m ) [ 0 ] ;
1555+ f . _mmproj_path = selectedProjector ? _selectedGgufExpr ( m , repo , selectedProjector . rel_path ) : '' ;
15501556 }
15511557 if ( f . reasoning_parser ) {
15521558 const _rpEl2 = panel . querySelector ( '[data-field="reasoning_parser"]' ) ;
@@ -1562,8 +1568,11 @@ function _rerenderCachedModels() {
15621568 }
15631569 let cmd = _buildServeCmd ( f , serveModel , backend ) ;
15641570 if ( f . extra && f . extra . trim ( ) ) cmd += ' ' + f . extra . trim ( ) ;
1565- const _ce2 = panel . querySelector ( '.hwfit-serve-cmd' ) ; _ce2 . value = _formatServeCmdPreview ( cmd ) ; _ce2 . style . height = 'auto' ; _ce2 . style . height = _ce2 . scrollHeight + 'px' ;
1566- panel . _cmd = cmd ;
1571+ const missingVisionProjector = backend === 'llamacpp' && ! ! f . vision && ! f . _mmproj_path ;
1572+ panel . _visionMissingProjector = missingVisionProjector ;
1573+ const _visionWarn = panel . querySelector ( '.hwfit-serve-vision-warn' ) ;
1574+ if ( _visionWarn ) _visionWarn . style . display = missingVisionProjector ? 'flex' : 'none' ;
1575+ const _ce2 = panel . querySelector ( '.hwfit-serve-cmd' ) ; _ce2 . value = _formatServeCmdPreview ( cmd ) ; _ce2 . style . height = 'auto' ; _ce2 . style . height = _ce2 . scrollHeight + 'px' ; panel . _cmd = cmd ;
15671576 panel . _host = f . host || '' ;
15681577 return cmd ;
15691578 }
@@ -2937,12 +2946,16 @@ function _rerenderCachedModels() {
29372946 } ) ;
29382947 serveState . backend = serveState . backend || ( _detectBackend ( m ) . backend ) || 'vllm' ;
29392948 const launchTarget = _selectedServeTarget ( panel ) ;
2949+ if ( serveState . backend === 'llamacpp' && serveState . vision && ! / (?: ^ | \s ) (?: - - m m p r o j | - - c l i p _ m o d e l _ p a t h ) \b / . test ( launchCmd ) ) {
2950+ _restoreLaunchBtn ( ) ;
2951+ uiModule . showToast ( 'Vision is checked, but no mmproj projector is in the launch command. Refresh cached models after downloading mmproj, or add --mmproj manually.' , 8000 ) ;
2952+ return ;
2953+ }
29402954 if ( serveState . backend === 'diffusers' && _remoteWindowsDiffusersUnsupported ( launchTarget ) ) {
29412955 _restoreLaunchBtn ( ) ;
29422956 uiModule . showToast ( 'Diffusers serving is not supported on remote Windows servers yet. Use local Windows or a Linux server.' , 9000 ) ;
29432957 return ;
29442958 }
2945-
29462959 // Pre-launch: check our own task list for a serve already running
29472960 // on this host. Offer to stop+launch as the default action — the
29482961 // SSH-based port probe below is more thorough but it can miss
0 commit comments