@@ -177,7 +177,8 @@ function isInactiveChecked(type) {
177
177
}
178
178
179
179
// Enhanced fetch with timeout and better error handling
180
- function fetchWithTimeout ( url , options = { } , timeout = 10000 ) {
180
+ function fetchWithTimeout ( url , options = { } , timeout = 30000 ) {
181
+ // Increased from 10000
181
182
const controller = new AbortController ( ) ;
182
183
const timeoutId = setTimeout ( ( ) => {
183
184
console . warn ( `Request to ${ url } timed out after ${ timeout } ms` ) ;
@@ -196,32 +197,41 @@ function fetchWithTimeout(url, options = {}, timeout = 10000) {
196
197
} )
197
198
. then ( ( response ) => {
198
199
clearTimeout ( timeoutId ) ;
199
- if (
200
- response . status === 0 ||
201
- ( response . ok && response . status === 200 )
202
- ) {
200
+
201
+ // FIX: Better handling of empty responses
202
+ if ( response . status === 0 ) {
203
+ // Status 0 often indicates a network error or CORS issue
204
+ throw new Error (
205
+ "Network error or server is not responding. Please ensure the server is running and accessible." ,
206
+ ) ;
207
+ }
208
+
209
+ if ( response . ok && response . status === 200 ) {
203
210
const contentLength = response . headers . get ( "content-length" ) ;
204
211
205
212
// Check Content-Length if present
206
- if ( contentLength !== null ) {
207
- if ( parseInt ( contentLength , 10 ) === 0 ) {
208
- throw new Error (
209
- "Server returned an empty response (via header)" ,
210
- ) ;
211
- }
212
- } else {
213
- // Fallback: check actual body
214
- const cloned = response . clone ( ) ;
215
- return cloned . text ( ) . then ( ( text ) => {
216
- if ( ! text . trim ( ) ) {
217
- throw new Error (
218
- "Server returned an empty response (via body)" ,
219
- ) ;
220
- }
221
- return response ;
222
- } ) ;
213
+ if (
214
+ contentLength !== null &&
215
+ parseInt ( contentLength , 10 ) === 0
216
+ ) {
217
+ console . warn (
218
+ `Empty response from ${ url } (Content-Length: 0)` ,
219
+ ) ;
220
+ // Don't throw error for intentionally empty responses
221
+ return response ;
223
222
}
223
+
224
+ // For responses without Content-Length, clone and check
225
+ const cloned = response . clone ( ) ;
226
+ return cloned . text ( ) . then ( ( text ) => {
227
+ if ( ! text || ! text . trim ( ) ) {
228
+ console . warn ( `Empty response body from ${ url } ` ) ;
229
+ // Return the original response anyway
230
+ }
231
+ return response ;
232
+ } ) ;
224
233
}
234
+
225
235
return response ;
226
236
} )
227
237
. catch ( ( error ) => {
@@ -230,15 +240,21 @@ function fetchWithTimeout(url, options = {}, timeout = 10000) {
230
240
// Improve error messages for common issues
231
241
if ( error . name === "AbortError" ) {
232
242
throw new Error (
233
- `Request timed out after ${ timeout / 1000 } seconds` ,
243
+ `Request timed out after ${ timeout / 1000 } seconds. The server may be slow or unresponsive. ` ,
234
244
) ;
235
- } else if ( error . message . includes ( "Failed to fetch" ) ) {
245
+ } else if (
246
+ error . message . includes ( "Failed to fetch" ) ||
247
+ error . message . includes ( "NetworkError" )
248
+ ) {
236
249
throw new Error (
237
- "Unable to connect to server. Please check if the server is running." ,
250
+ "Unable to connect to server. Please check if the server is running on the correct port ." ,
238
251
) ;
239
- } else if ( error . message . includes ( "empty response" ) ) {
252
+ } else if (
253
+ error . message . includes ( "empty response" ) ||
254
+ error . message . includes ( "ERR_EMPTY_RESPONSE" )
255
+ ) {
240
256
throw new Error (
241
- "Server returned an empty response. The endpoint may not be implemented." ,
257
+ "Server returned an empty response. This endpoint may not be implemented yet or the server crashed ." ,
242
258
) ;
243
259
}
244
260
@@ -510,8 +526,8 @@ function resetModalState(modalId) {
510
526
// More robust metrics request tracking
511
527
let metricsRequestController = null ;
512
528
let metricsRequestPromise = null ;
513
- const MAX_METRICS_RETRIES = 2 ; // Reduced from 3
514
- const METRICS_RETRY_DELAY = 1500 ; // Reduced from 2000ms
529
+ const MAX_METRICS_RETRIES = 3 ; // Increased from 2
530
+ const METRICS_RETRY_DELAY = 2000 ; // Increased from 1500ms
515
531
516
532
/**
517
533
* Enhanced metrics loading with better race condition prevention
@@ -548,7 +564,7 @@ async function loadMetricsInternal() {
548
564
const result = await fetchWithTimeoutAndRetry (
549
565
`${ window . ROOT_PATH } /admin/metrics` ,
550
566
{ } , // options
551
- 20000 , // 20 second timeout (reduced from 30 )
567
+ 45000 , // Increased timeout specifically for metrics (was 20000 )
552
568
MAX_METRICS_RETRIES ,
553
569
) ;
554
570
@@ -558,10 +574,30 @@ async function loadMetricsInternal() {
558
574
showMetricsPlaceholder ( ) ;
559
575
return ;
560
576
}
577
+ // FIX: Handle 500 errors specifically
578
+ if ( result . status >= 500 ) {
579
+ throw new Error (
580
+ `Server error (${ result . status } ). The metrics calculation may have failed.` ,
581
+ ) ;
582
+ }
561
583
throw new Error ( `HTTP ${ result . status } : ${ result . statusText } ` ) ;
562
584
}
563
585
564
- const data = await result . json ( ) ;
586
+ // FIX: Handle empty or invalid JSON responses
587
+ let data ;
588
+ try {
589
+ const text = await result . text ( ) ;
590
+ if ( ! text || ! text . trim ( ) ) {
591
+ console . warn ( "Empty metrics response, using default data" ) ;
592
+ data = { } ; // Use empty object as fallback
593
+ } else {
594
+ data = JSON . parse ( text ) ;
595
+ }
596
+ } catch ( parseError ) {
597
+ console . error ( "Failed to parse metrics JSON:" , parseError ) ;
598
+ data = { } ; // Use empty object as fallback
599
+ }
600
+
565
601
displayMetrics ( data ) ;
566
602
console . log ( "✓ Metrics loaded successfully" ) ;
567
603
} catch ( error ) {
@@ -745,6 +781,25 @@ function displayMetrics(data) {
745
781
}
746
782
747
783
try {
784
+ // FIX: Handle completely empty data
785
+ if ( ! data || Object . keys ( data ) . length === 0 ) {
786
+ const emptyStateDiv = document . createElement ( "div" ) ;
787
+ emptyStateDiv . className = "text-center p-8 text-gray-500" ;
788
+ emptyStateDiv . innerHTML = `
789
+ <svg class="mx-auto h-12 w-12 text-gray-400 mb-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
790
+ <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"></path>
791
+ </svg>
792
+ <h3 class="text-lg font-medium mb-2">No Metrics Available</h3>
793
+ <p class="text-sm">Metrics data will appear here once tools, resources, or prompts are executed.</p>
794
+ <button onclick="retryLoadMetrics()" class="mt-4 bg-indigo-600 text-white px-4 py-2 rounded hover:bg-indigo-700 transition-colors">
795
+ Refresh Metrics
796
+ </button>
797
+ ` ;
798
+ metricsPanel . innerHTML = "" ;
799
+ metricsPanel . appendChild ( emptyStateDiv ) ;
800
+ return ;
801
+ }
802
+
748
803
// Create main container with safe structure
749
804
const mainContainer = document . createElement ( "div" ) ;
750
805
mainContainer . className = "space-y-6" ;
@@ -2863,8 +2918,8 @@ function handleSubmitWithConfirmation(event, type) {
2863
2918
const toolTestState = {
2864
2919
activeRequests : new Map ( ) , // toolId -> AbortController
2865
2920
lastRequestTime : new Map ( ) , // toolId -> timestamp
2866
- debounceDelay : 500 , // ms
2867
- requestTimeout : 10000 , // Reduced from 15000ms
2921
+ debounceDelay : 1000 , // Increased from 500ms
2922
+ requestTimeout : 30000 , // Increased from 10000ms
2868
2923
} ;
2869
2924
2870
2925
/**
@@ -2878,7 +2933,7 @@ async function testTool(toolId) {
2878
2933
const now = Date . now ( ) ;
2879
2934
const lastRequest = toolTestState . lastRequestTime . get ( toolId ) || 0 ;
2880
2935
const timeSinceLastRequest = now - lastRequest ;
2881
- const enhancedDebounceDelay = 1000 ; // Increased from 500ms
2936
+ const enhancedDebounceDelay = 2000 ; // Increased from 1000ms
2882
2937
2883
2938
if ( timeSinceLastRequest < enhancedDebounceDelay ) {
2884
2939
console . log (
@@ -2888,7 +2943,7 @@ async function testTool(toolId) {
2888
2943
( enhancedDebounceDelay - timeSinceLastRequest ) / 1000 ,
2889
2944
) ;
2890
2945
showErrorMessage (
2891
- `Please wait ${ waitTime } more seconds before testing again` ,
2946
+ `Please wait ${ waitTime } more second ${ waitTime > 1 ? "s" : "" } before testing again` ,
2892
2947
) ;
2893
2948
return ;
2894
2949
}
@@ -2928,7 +2983,7 @@ async function testTool(toolId) {
2928
2983
toolTestState . activeRequests . set ( toolId , controller ) ;
2929
2984
toolTestState . lastRequestTime . set ( toolId , now ) ;
2930
2985
2931
- // 6. MAKE REQUEST with increased timeout (was 10 seconds, now 15)
2986
+ // 6. MAKE REQUEST with increased timeout
2932
2987
const response = await fetchWithTimeout (
2933
2988
`${ window . ROOT_PATH } /admin/tools/${ toolId } ` ,
2934
2989
{
@@ -2938,7 +2993,7 @@ async function testTool(toolId) {
2938
2993
Pragma : "no-cache" ,
2939
2994
} ,
2940
2995
} ,
2941
- 15000 , // Increased timeout
2996
+ toolTestState . requestTimeout , // Use the increased timeout
2942
2997
) ;
2943
2998
2944
2999
if ( ! response . ok ) {
@@ -3165,7 +3220,7 @@ async function runToolTest() {
3165
3220
params,
3166
3221
} ;
3167
3222
3168
- // Use shorter timeout for test execution
3223
+ // Use longer timeout for test execution
3169
3224
const response = await fetchWithTimeout (
3170
3225
`${ window . ROOT_PATH } /rpc` ,
3171
3226
{
@@ -3176,8 +3231,8 @@ async function runToolTest() {
3176
3231
body : JSON . stringify ( payload ) ,
3177
3232
credentials : "include" ,
3178
3233
} ,
3179
- 8000 ,
3180
- ) ; // 8 second timeout
3234
+ 20000 , // Increased from 8000
3235
+ ) ;
3181
3236
3182
3237
const result = await response . json ( ) ;
3183
3238
const resultStr = JSON . stringify ( result , null , 2 ) ;
@@ -4183,6 +4238,7 @@ function setupTooltipsWithAlpine() {
4183
4238
4184
4239
Alpine . directive ( "tooltip" , ( el , { expression } , { evaluate } ) => {
4185
4240
let tooltipEl = null ;
4241
+ let animationFrameId = null ; // Track animation frame
4186
4242
4187
4243
const moveTooltip = ( e ) => {
4188
4244
if ( ! tooltipEl ) {
@@ -4234,9 +4290,19 @@ function setupTooltipsWithAlpine() {
4234
4290
tooltipEl . style . top = `${ rect . bottom + scrollY + 10 } px` ;
4235
4291
}
4236
4292
4237
- requestAnimationFrame ( ( ) => {
4238
- tooltipEl . style . opacity = "1" ;
4293
+ // FIX: Cancel any pending animation frame before setting a new one
4294
+ if ( animationFrameId ) {
4295
+ cancelAnimationFrame ( animationFrameId ) ;
4296
+ }
4297
+
4298
+ animationFrameId = requestAnimationFrame ( ( ) => {
4299
+ // FIX: Check if tooltipEl still exists before accessing its style
4300
+ if ( tooltipEl ) {
4301
+ tooltipEl . style . opacity = "1" ;
4302
+ }
4303
+ animationFrameId = null ;
4239
4304
} ) ;
4305
+
4240
4306
window . addEventListener ( "scroll" , hideTooltip , {
4241
4307
passive : true ,
4242
4308
} ) ;
@@ -4250,15 +4316,28 @@ function setupTooltipsWithAlpine() {
4250
4316
return ;
4251
4317
}
4252
4318
4319
+ // FIX: Cancel any pending animation frame
4320
+ if ( animationFrameId ) {
4321
+ cancelAnimationFrame ( animationFrameId ) ;
4322
+ animationFrameId = null ;
4323
+ }
4324
+
4253
4325
tooltipEl . style . opacity = "0" ;
4254
4326
el . removeEventListener ( "mousemove" , moveTooltip ) ;
4255
4327
window . removeEventListener ( "scroll" , hideTooltip ) ;
4256
4328
window . removeEventListener ( "resize" , hideTooltip ) ;
4257
- el . addEventListener ( "click" , hideTooltip ) ;
4329
+ el . removeEventListener ( "click" , hideTooltip ) ;
4330
+
4258
4331
const toRemove = tooltipEl ;
4259
- tooltipEl = null ;
4260
- setTimeout ( ( ) => toRemove . remove ( ) , 200 ) ;
4332
+ tooltipEl = null ; // Set to null immediately
4333
+
4334
+ setTimeout ( ( ) => {
4335
+ if ( toRemove && toRemove . parentNode ) {
4336
+ toRemove . parentNode . removeChild ( toRemove ) ;
4337
+ }
4338
+ } , 200 ) ;
4261
4339
} ;
4340
+
4262
4341
el . addEventListener ( "mouseenter" , showTooltip ) ;
4263
4342
el . addEventListener ( "mouseleave" , hideTooltip ) ;
4264
4343
el . addEventListener ( "focus" , showTooltip ) ;
0 commit comments