11import { PropsService } from '$lib/services/props.service' ;
22import { ServerRole } from '$lib/enums' ;
3+ import { ApiError } from '$lib/utils/api-fetch' ;
4+
5+ const LOADING_RETRY_INTERVAL_MS = 1000 ;
36
47/**
58 * serverStore - Server connection state, configuration, and role detection
@@ -29,8 +32,10 @@ class ServerStore {
2932 props = $state < ApiLlamaCppServerProps | null > ( null ) ;
3033 loading = $state ( false ) ;
3134 error = $state < string | null > ( null ) ;
35+ status = $state < number | null > ( null ) ;
3236 role = $state < ServerRole | null > ( null ) ;
3337 private fetchPromise : Promise < void > | null = null ;
38+ private retryTimer : ReturnType < typeof setTimeout > | null = null ;
3439
3540 /**
3641 *
@@ -70,23 +75,43 @@ class ServerStore {
7075 *
7176 */
7277
73- async fetch ( ) : Promise < void > {
78+ /**
79+ * @param background - Set by the automatic "still loading" poll. Skips the
80+ * `loading` flag flip so the UI doesn't bounce between the full loading
81+ * splash and the chat screen every retry tick.
82+ */
83+ async fetch ( { background = false } : { background ?: boolean } = { } ) : Promise < void > {
7484 if ( this . fetchPromise ) return this . fetchPromise ;
7585
76- this . loading = true ;
77- this . error = null ;
86+ this . clearRetryTimer ( ) ;
87+ if ( ! background ) {
88+ this . loading = true ;
89+ }
90+ // Don't clear an existing "still loading" error before a retry -
91+ // doing so would unmount/remount the error banner every second.
92+ if ( this . status !== 503 ) {
93+ this . error = null ;
94+ }
7895
7996 const fetchPromise = ( async ( ) => {
8097 try {
8198 const props = await PropsService . fetch ( ) ;
8299 this . props = props ;
83100 this . error = null ;
101+ this . status = null ;
84102 this . detectRole ( props ) ;
85103 } catch ( error : unknown ) {
86104 this . error = error instanceof Error ? error . message : String ( error ) ;
105+ this . status = error instanceof ApiError ? error . status : null ;
87106 console . error ( 'Error fetching server properties:' , error ) ;
107+
108+ if ( this . status === 503 ) {
109+ this . scheduleRetry ( ) ;
110+ }
88111 } finally {
89- this . loading = false ;
112+ if ( ! background ) {
113+ this . loading = false ;
114+ }
90115 this . fetchPromise = null ;
91116 }
92117 } ) ( ) ;
@@ -96,13 +121,30 @@ class ServerStore {
96121 }
97122
98123 clear ( ) : void {
124+ this . clearRetryTimer ( ) ;
99125 this . props = null ;
100126 this . error = null ;
127+ this . status = null ;
101128 this . loading = false ;
102129 this . role = null ;
103130 this . fetchPromise = null ;
104131 }
105132
133+ private scheduleRetry ( ) : void {
134+ if ( this . retryTimer ) return ;
135+ this . retryTimer = setTimeout ( ( ) => {
136+ this . retryTimer = null ;
137+ this . fetch ( { background : true } ) ;
138+ } , LOADING_RETRY_INTERVAL_MS ) ;
139+ }
140+
141+ private clearRetryTimer ( ) : void {
142+ if ( this . retryTimer ) {
143+ clearTimeout ( this . retryTimer ) ;
144+ this . retryTimer = null ;
145+ }
146+ }
147+
106148 /**
107149 *
108150 *
@@ -125,6 +167,7 @@ export const serverStore = new ServerStore();
125167export const serverProps = ( ) => serverStore . props ;
126168export const serverLoading = ( ) => serverStore . loading ;
127169export const serverError = ( ) => serverStore . error ;
170+ export const serverStatus = ( ) => serverStore . status ;
128171export const serverRole = ( ) => serverStore . role ;
129172export const defaultParams = ( ) => serverStore . defaultParams ;
130173export const contextSize = ( ) => serverStore . contextSize ;
0 commit comments