@@ -3,6 +3,7 @@ import { useEffect, useState, useRef, useMemo, useCallback } from 'react';
33import type { KeyboardEvent } from 'react' ;
44import type { ModelInfo } from '~/lib/modules/llm/types' ;
55import { classNames } from '~/utils/classNames' ;
6+ import { LOCAL_PROVIDERS } from '~/lib/stores/settings' ;
67
78// Fuzzy search utilities
89const levenshteinDistance = ( str1 : string , str2 : string ) : number => {
@@ -130,6 +131,32 @@ export const ModelSelector = ({
130131 const providerDropdownRef = useRef < HTMLDivElement > ( null ) ;
131132 const [ showFreeModelsOnly , setShowFreeModelsOnly ] = useState ( false ) ;
132133
134+ type ConnectionStatus = 'unknown' | 'connected' | 'disconnected' ;
135+
136+ const [ localProviderStatus , setLocalProviderStatus ] = useState < Record < string , ConnectionStatus > > ( { } ) ;
137+
138+ // Check connectivity of local providers when provider list changes
139+ useEffect ( ( ) => {
140+ const checkLocalProviders = async ( ) => {
141+ const statuses : Record < string , 'connected' | 'disconnected' > = { } ;
142+
143+ for ( const p of providerList ) {
144+ if ( ! LOCAL_PROVIDERS . includes ( p . name ) ) {
145+ continue ;
146+ }
147+
148+ // If the provider has models loaded, it's connected
149+ const hasModels = modelList . some ( ( m ) => m . provider === p . name ) ;
150+
151+ statuses [ p . name ] = hasModels ? 'connected' : 'disconnected' ;
152+ }
153+
154+ setLocalProviderStatus ( statuses ) ;
155+ } ;
156+
157+ checkLocalProviders ( ) ;
158+ } , [ providerList , modelList ] ) ;
159+
133160 // Debounce search queries
134161 useEffect ( ( ) => {
135162 const timer = setTimeout ( ( ) => {
@@ -440,7 +467,28 @@ export const ModelSelector = ({
440467 tabIndex = { 0 }
441468 >
442469 < div className = "flex items-center justify-between" >
443- < div className = "truncate" > { provider ?. name || 'Select provider' } </ div >
470+ < div className = "flex items-center gap-2 truncate" >
471+ { provider ?. name && LOCAL_PROVIDERS . includes ( provider . name ) && (
472+ < span
473+ className = { classNames (
474+ 'inline-block w-2 h-2 rounded-full flex-shrink-0' ,
475+ localProviderStatus [ provider . name ] === 'connected'
476+ ? 'bg-green-500'
477+ : localProviderStatus [ provider . name ] === 'disconnected'
478+ ? 'bg-red-400'
479+ : 'bg-bolt-elements-textTertiary' ,
480+ ) }
481+ title = {
482+ localProviderStatus [ provider . name ] === 'connected'
483+ ? `${ provider . name } is running`
484+ : localProviderStatus [ provider . name ] === 'disconnected'
485+ ? `${ provider . name } is not reachable`
486+ : 'Checking...'
487+ }
488+ />
489+ ) }
490+ { provider ?. name || 'Select provider' }
491+ </ div >
444492 < div
445493 className = { classNames (
446494 'i-ph:caret-down w-4 h-4 text-bolt-elements-textSecondary opacity-75' ,
@@ -559,11 +607,25 @@ export const ModelSelector = ({
559607 } }
560608 tabIndex = { focusedProviderIndex === index ? 0 : - 1 }
561609 >
562- < div
563- dangerouslySetInnerHTML = { {
564- __html : ( providerOption as any ) . highlightedName || providerOption . name ,
565- } }
566- />
610+ < div className = "flex items-center gap-2" >
611+ { LOCAL_PROVIDERS . includes ( providerOption . name ) && (
612+ < span
613+ className = { classNames (
614+ 'inline-block w-2 h-2 rounded-full flex-shrink-0' ,
615+ localProviderStatus [ providerOption . name ] === 'connected'
616+ ? 'bg-green-500'
617+ : localProviderStatus [ providerOption . name ] === 'disconnected'
618+ ? 'bg-red-400'
619+ : 'bg-bolt-elements-textTertiary' ,
620+ ) }
621+ />
622+ ) }
623+ < span
624+ dangerouslySetInnerHTML = { {
625+ __html : ( providerOption as any ) . highlightedName || providerOption . name ,
626+ } }
627+ />
628+ </ div >
567629 </ div >
568630 ) )
569631 ) }
@@ -717,8 +779,17 @@ export const ModelSelector = ({
717779 ? `No models match "${ debouncedModelSearchQuery } "${ showFreeModelsOnly ? ' (free only)' : '' } `
718780 : showFreeModelsOnly
719781 ? 'No free models available'
720- : 'No models available' }
782+ : provider ?. name && LOCAL_PROVIDERS . includes ( provider . name )
783+ ? `No models found — is ${ provider . name } running?`
784+ : 'No models available' }
721785 </ div >
786+ { ! debouncedModelSearchQuery && provider ?. name && LOCAL_PROVIDERS . includes ( provider . name ) && (
787+ < div className = "text-xs text-bolt-elements-textTertiary mt-1" >
788+ Make sure { provider . name } is running and has at least one model loaded.
789+ { provider . name === 'Ollama' && ' Try: ollama pull llama3.2' }
790+ { provider . name === 'LMStudio' && ' Load a model in LM Studio first.' }
791+ </ div >
792+ ) }
722793 { debouncedModelSearchQuery && (
723794 < div className = "text-xs text-bolt-elements-textTertiary" >
724795 Try searching for model names, context sizes (e.g., "128k", "1M"), or capabilities
0 commit comments