@@ -8,16 +8,35 @@ import {
88} from "../../lib/api.js" ;
99import { showToast } from "../toast.js" ;
1010import { useCachedFetch } from "../../hooks/use-cached-fetch.js" ;
11+ import { usePolling } from "../../hooks/usePolling.js" ;
12+ import { invalidateCache } from "../../lib/api-cache.js" ;
13+ import {
14+ getModelCatalogModels ,
15+ isModelCatalogRefreshing ,
16+ kModelCatalogCacheKey ,
17+ kModelCatalogPollIntervalMs ,
18+ } from "../../lib/model-catalog.js" ;
1119
1220let kModelsTabCache = null ;
1321const getCredentialValue = ( value ) =>
1422 String ( value ?. key || value ?. token || value ?. access || "" ) . trim ( ) ;
23+ const kNoModelsFoundError = "No models found" ;
24+ const kModelSettingsLoadError = "Failed to load model settings" ;
1525
1626export const useModels = ( agentId ) => {
1727 const isScoped = ! ! agentId ;
1828 const normalizedAgentId = String ( agentId || "" ) . trim ( ) ;
1929 const useCache = ! isScoped ;
2030 const [ catalog , setCatalog ] = useState ( ( ) => ( useCache && kModelsTabCache ?. catalog ) || [ ] ) ;
31+ const [ catalogStatus , setCatalogStatus ] = useState (
32+ ( ) =>
33+ ( useCache && kModelsTabCache ?. catalogStatus ) || {
34+ source : "" ,
35+ fetchedAt : null ,
36+ stale : false ,
37+ refreshing : false ,
38+ } ,
39+ ) ;
2140 const [ primary , setPrimary ] = useState ( ( ) => ( useCache && kModelsTabCache ?. primary ) || "" ) ;
2241 const [ configuredModels , setConfiguredModels ] = useState (
2342 ( ) => ( useCache && kModelsTabCache ?. configuredModels ) || { } ,
@@ -48,7 +67,7 @@ export const useModels = (agentId) => {
4867 const modelsConfigCacheKey = normalizedAgentId
4968 ? `/api/models/config?agentId=${ encodeURIComponent ( normalizedAgentId ) } `
5069 : "/api/models/config" ;
51- const catalogFetchState = useCachedFetch ( "/api/models" , fetchModels , {
70+ const catalogFetchState = useCachedFetch ( kModelCatalogCacheKey , fetchModels , {
5271 maxAgeMs : 30000 ,
5372 } ) ;
5473 const configFetchState = useCachedFetch (
@@ -59,6 +78,41 @@ export const useModels = (agentId) => {
5978 const codexFetchState = useCachedFetch ( "/api/codex/status" , fetchCodexStatus , {
6079 maxAgeMs : 15000 ,
6180 } ) ;
81+ const catalogPoll = usePolling ( fetchModels , kModelCatalogPollIntervalMs , {
82+ enabled : ready && isModelCatalogRefreshing ( catalogStatus ) ,
83+ pauseWhenHidden : true ,
84+ cacheKey : kModelCatalogCacheKey ,
85+ } ) ;
86+
87+ const syncCatalogError = useCallback ( ( catalogModels ) => {
88+ setError ( ( current ) => {
89+ if ( catalogModels . length > 0 ) {
90+ return current === kNoModelsFoundError ? "" : current ;
91+ }
92+ return current || kNoModelsFoundError ;
93+ } ) ;
94+ } , [ ] ) ;
95+
96+ const applyCatalogResult = useCallback (
97+ ( catalogResult ) => {
98+ const catalogModels = getModelCatalogModels ( catalogResult ) ;
99+ const nextCatalogStatus = {
100+ source : String ( catalogResult ?. source || "" ) ,
101+ fetchedAt : Number ( catalogResult ?. fetchedAt || 0 ) || null ,
102+ stale : Boolean ( catalogResult ?. stale ) ,
103+ refreshing : Boolean ( catalogResult ?. refreshing ) ,
104+ } ;
105+ setCatalog ( catalogModels ) ;
106+ setCatalogStatus ( nextCatalogStatus ) ;
107+ updateCache ( {
108+ catalog : catalogModels ,
109+ catalogStatus : nextCatalogStatus ,
110+ } ) ;
111+ syncCatalogError ( catalogModels ) ;
112+ return catalogModels ;
113+ } ,
114+ [ syncCatalogError , updateCache ] ,
115+ ) ;
62116
63117 const refresh = useCallback ( async ( ) => {
64118 if ( ! ready ) setLoading ( true ) ;
@@ -69,10 +123,7 @@ export const useModels = (agentId) => {
69123 configFetchState . refresh ( { force : true } ) ,
70124 codexFetchState . refresh ( { force : true } ) ,
71125 ] ) ;
72- const catalogModels = Array . isArray ( catalogResult . models )
73- ? catalogResult . models
74- : [ ] ;
75- setCatalog ( catalogModels ) ;
126+ const catalogModels = applyCatalogResult ( catalogResult ) ;
76127 const p = configResult . primary || "" ;
77128 const cm = configResult . configuredModels || { } ;
78129 const ap = configResult . authProfiles || [ ] ;
@@ -94,20 +145,31 @@ export const useModels = (agentId) => {
94145 authOrder : ao ,
95146 codexStatus : codex || { connected : false } ,
96147 } ) ;
97- if ( ! catalogModels . length ) setError ( "No models found" ) ;
98148 } catch ( err ) {
99- setError ( "Failed to load model settings" ) ;
100- showToast ( `Failed to load model settings : ${ err . message } ` , "error" ) ;
149+ setError ( kModelSettingsLoadError ) ;
150+ showToast ( `${ kModelSettingsLoadError } : ${ err . message } ` , "error" ) ;
101151 } finally {
102152 setReady ( true ) ;
103153 setLoading ( false ) ;
104154 }
105- } , [ catalogFetchState , codexFetchState , configFetchState , ready , updateCache , agentId , isScoped ] ) ;
155+ } , [
156+ applyCatalogResult ,
157+ catalogFetchState ,
158+ codexFetchState ,
159+ configFetchState ,
160+ ready ,
161+ updateCache ,
162+ ] ) ;
106163
107164 useEffect ( ( ) => {
108165 refresh ( ) ;
109166 } , [ agentId ] ) ;
110167
168+ useEffect ( ( ) => {
169+ if ( ! catalogPoll . data ) return ;
170+ applyCatalogResult ( catalogPoll . data ) ;
171+ } , [ applyCatalogResult , catalogPoll . data ] ) ;
172+
111173 const stableStringify = ( obj ) =>
112174 JSON . stringify ( Object . keys ( obj ) . sort ( ) . reduce ( ( acc , k ) => { acc [ k ] = obj [ k ] ; return acc ; } , { } ) ) ;
113175
@@ -261,6 +323,7 @@ export const useModels = (agentId) => {
261323 if ( result . syncWarning ) {
262324 showToast ( `Saved, but git-sync failed: ${ result . syncWarning } ` , "warning" ) ;
263325 }
326+ invalidateCache ( kModelCatalogCacheKey ) ;
264327 await refresh ( ) ;
265328 } catch ( err ) {
266329 showToast ( err . message || "Failed to save changes" , "error" ) ;
@@ -274,6 +337,8 @@ export const useModels = (agentId) => {
274337 profileEdits ,
275338 orderEdits ,
276339 authProfiles ,
340+ isScoped ,
341+ agentId ,
277342 refresh ,
278343 ] ) ;
279344
0 commit comments