diff --git a/ccw/src/templates/dashboard-js/views/api-settings.js b/ccw/src/templates/dashboard-js/views/api-settings.js
index b01276fd..e8cb615a 100644
--- a/ccw/src/templates/dashboard-js/views/api-settings.js
+++ b/ccw/src/templates/dashboard-js/views/api-settings.js
@@ -18,13 +18,26 @@ let embeddingPoolConfig = null;
let embeddingPoolAvailableModels = [];
let embeddingPoolDiscoveredProviders = [];
+// Cache for ccw-litellm status (frontend cache with TTL)
+let ccwLitellmStatusCache = null;
+let ccwLitellmStatusCacheTime = 0;
+const CCW_LITELLM_STATUS_CACHE_TTL = 60000; // 60 seconds
+
// ========== Data Loading ==========
/**
* Load API configuration
+ * @param {boolean} forceRefresh - Force refresh from server, bypass cache
*/
-async function loadApiSettings() {
+async function loadApiSettings(forceRefresh = false) {
+ // If not forcing refresh and data already exists, return cached data
+ if (!forceRefresh && apiSettingsData && apiSettingsData.providers) {
+ console.log('[API Settings] Using cached API settings data');
+ return apiSettingsData;
+ }
+
try {
+ console.log('[API Settings] Fetching API settings from server...');
const response = await fetch('/api/litellm-api/config');
if (!response.ok) throw new Error('Failed to load API settings');
apiSettingsData = await response.json();
@@ -142,6 +155,9 @@ async function saveEmbeddingPoolConfig() {
const syncCount = result.syncResult?.syncedEndpoints?.length || 0;
showRefreshToast(t('apiSettings.poolSaved') + (syncCount > 0 ? ' (' + syncCount + ' endpoints synced)' : ''), 'success');
+ // Invalidate API settings cache since endpoints may have been synced
+ apiSettingsData = null;
+
// Reload the embedding pool section
await renderEmbeddingPoolMainPanel();
@@ -440,6 +456,8 @@ async function saveProvider() {
showRefreshToast(t('apiSettings.providerSaved'), 'success');
closeProviderModal();
+ // Force refresh data after saving
+ apiSettingsData = null;
await renderApiSettings();
} catch (err) {
console.error('Failed to save provider:', err);
@@ -461,6 +479,8 @@ async function deleteProvider(providerId) {
if (!response.ok) throw new Error('Failed to delete provider');
showRefreshToast(t('apiSettings.providerDeleted'), 'success');
+ // Force refresh data after deleting
+ apiSettingsData = null;
await renderApiSettings();
} catch (err) {
console.error('Failed to delete provider:', err);
@@ -778,6 +798,8 @@ async function saveEndpoint() {
showRefreshToast(t('apiSettings.endpointSaved'), 'success');
closeEndpointModal();
+ // Force refresh data after saving
+ apiSettingsData = null;
await renderApiSettings();
} catch (err) {
console.error('Failed to save endpoint:', err);
@@ -799,6 +821,8 @@ async function deleteEndpoint(endpointId) {
if (!response.ok) throw new Error('Failed to delete endpoint');
showRefreshToast(t('apiSettings.endpointDeleted'), 'success');
+ // Force refresh data after deleting
+ apiSettingsData = null;
await renderApiSettings();
} catch (err) {
console.error('Failed to delete endpoint:', err);
@@ -872,6 +896,7 @@ async function clearCache() {
const result = await response.json();
showRefreshToast(t('apiSettings.cacheCleared') + ' (' + result.removed + ' entries)', 'success');
+ // Cache stats might have changed, but apiSettingsData doesn't need refresh
await renderApiSettings();
} catch (err) {
console.error('Failed to clear cache:', err);
@@ -918,8 +943,8 @@ async function renderApiSettings() {
if (statsGrid) statsGrid.style.display = 'none';
if (searchInput) searchInput.parentElement.style.display = 'none';
- // Load data
- await loadApiSettings();
+ // Load data (use cache by default, forceRefresh=false)
+ await loadApiSettings(false);
if (!apiSettingsData) {
container.innerHTML = '
' +
@@ -1011,8 +1036,8 @@ async function renderApiSettings() {
renderCacheMainPanel();
}
- // Check and render ccw-litellm status
- checkCcwLitellmStatus().then(renderCcwLitellmStatusCard);
+ // Check and render ccw-litellm status (use cache by default)
+ checkCcwLitellmStatus(false).then(renderCcwLitellmStatusCard);
if (window.lucide) lucide.createIcons();
}
@@ -1364,12 +1389,17 @@ async function toggleProviderEnabled(providerId, enabled) {
});
if (!response.ok) throw new Error('Failed to update provider');
- // Update local data
+ // Update local data (for instant UI feedback)
var provider = apiSettingsData.providers.find(function(p) { return p.id === providerId; });
if (provider) provider.enabled = enabled;
renderProviderList();
showRefreshToast(t('apiSettings.providerUpdated'), 'success');
+
+ // Invalidate cache for next render
+ setTimeout(function() {
+ apiSettingsData = null;
+ }, 100);
} catch (err) {
console.error('Failed to toggle provider:', err);
showRefreshToast(t('common.error') + ': ' + err.message, 'error');
@@ -2047,7 +2077,7 @@ async function saveProviderApiBase(providerId) {
if (!response.ok) throw new Error('Failed to update API base');
- // Update local data
+ // Update local data (for instant UI feedback)
var provider = apiSettingsData.providers.find(function(p) { return p.id === providerId; });
if (provider) {
provider.apiBase = newApiBase || undefined;
@@ -2056,6 +2086,12 @@ async function saveProviderApiBase(providerId) {
// Update preview
updateApiBasePreview(newApiBase);
showRefreshToast(t('apiSettings.apiBaseUpdated'), 'success');
+
+ // Invalidate cache for next render (but keep current data for immediate UI)
+ // This ensures next tab switch or page refresh gets fresh data
+ setTimeout(function() {
+ apiSettingsData = null;
+ }, 100);
} catch (err) {
console.error('Failed to save API base:', err);
showRefreshToast(t('common.error') + ': ' + err.message, 'error');
@@ -3033,19 +3069,39 @@ function toggleKeyVisibility(btn) {
/**
* Check ccw-litellm installation status
+ * @param {boolean} forceRefresh - Force refresh from server, bypass cache
*/
-async function checkCcwLitellmStatus() {
+async function checkCcwLitellmStatus(forceRefresh = false) {
+ // Check if cache is valid and not forcing refresh
+ if (!forceRefresh && ccwLitellmStatusCache &&
+ (Date.now() - ccwLitellmStatusCacheTime < CCW_LITELLM_STATUS_CACHE_TTL)) {
+ console.log('[API Settings] Using cached ccw-litellm status');
+ window.ccwLitellmStatus = ccwLitellmStatusCache;
+ return ccwLitellmStatusCache;
+ }
+
try {
- console.log('[API Settings] Checking ccw-litellm status...');
+ console.log('[API Settings] Checking ccw-litellm status from server...');
var response = await fetch('/api/litellm-api/ccw-litellm/status');
console.log('[API Settings] Status response:', response.status);
var status = await response.json();
console.log('[API Settings] ccw-litellm status:', status);
+
+ // Update cache
+ ccwLitellmStatusCache = status;
+ ccwLitellmStatusCacheTime = Date.now();
window.ccwLitellmStatus = status;
+
return status;
} catch (e) {
console.warn('[API Settings] Could not check ccw-litellm status:', e);
- return { installed: false };
+ var fallbackStatus = { installed: false };
+
+ // Cache the fallback result too
+ ccwLitellmStatusCache = fallbackStatus;
+ ccwLitellmStatusCacheTime = Date.now();
+
+ return fallbackStatus;
}
}
@@ -3106,8 +3162,8 @@ async function installCcwLitellm() {
if (result.success) {
showRefreshToast('ccw-litellm installed successfully!', 'success');
- // Refresh status
- await checkCcwLitellmStatus();
+ // Refresh status (force refresh after installation)
+ await checkCcwLitellmStatus(true);
renderCcwLitellmStatusCard();
} else {
showRefreshToast('Failed to install ccw-litellm: ' + result.error, 'error');