feat(ui): add data caching and hash-based routing

- Implement localStorage-based caching in data-store to restore accounts, models, and usage data on load, improving initial render performance
- Add hash-based routing in global store to sync active tab with URL, enabling browser back/forward navigation and direct linking to tabs
This commit is contained in:
jgor20
2026-01-11 13:47:15 +00:00
parent 11e256ac70
commit 0a0e3e2851
2 changed files with 70 additions and 1 deletions

View File

@@ -33,13 +33,54 @@ document.addEventListener('alpine:init', () => {
// For simplicity, let's keep relevant filters here.
init() {
// Restore from cache first for instant render
this.loadFromCache();
// Watch filters to recompute
// Alpine stores don't have $watch automatically unless inside a component?
// We can manually call compute when filters change.
},
loadFromCache() {
try {
const cached = localStorage.getItem('ag_data_cache');
if (cached) {
const data = JSON.parse(cached);
// Basic validty check
if (data.accounts && data.models) {
this.accounts = data.accounts;
this.models = data.models;
this.modelConfig = data.modelConfig || {};
this.usageHistory = data.usageHistory || {};
// Don't show loading on initial load if we have cache
this.initialLoad = false;
this.computeQuotaRows();
console.log('Restored data from cache');
}
}
} catch (e) {
console.warn('Failed to load cache', e);
}
},
saveToCache() {
try {
const cacheData = {
accounts: this.accounts,
models: this.models,
modelConfig: this.modelConfig,
usageHistory: this.usageHistory,
timestamp: Date.now()
};
localStorage.setItem('ag_data_cache', JSON.stringify(cacheData));
} catch (e) {
console.warn('Failed to save cache', e);
}
},
async fetchData() {
// Only show skeleton on initial load, not on refresh
// Only show skeleton on initial load if we didn't restore from cache
if (this.initialLoad) {
this.loading = true;
}
@@ -67,6 +108,7 @@ document.addEventListener('alpine:init', () => {
this.usageHistory = data.history;
}
this.saveToCache(); // Save fresh data
this.computeQuotaRows();
this.connectionStatus = 'connected';

View File

@@ -5,6 +5,33 @@
document.addEventListener('alpine:init', () => {
Alpine.store('global', {
init() {
// Hash-based routing
const validTabs = ['dashboard', 'models', 'accounts', 'logs', 'settings'];
const getHash = () => window.location.hash.substring(1);
// 1. Initial load from hash
const initialHash = getHash();
if (validTabs.includes(initialHash)) {
this.activeTab = initialHash;
}
// 2. Sync State -> URL
Alpine.effect(() => {
if (validTabs.includes(this.activeTab) && getHash() !== this.activeTab) {
window.location.hash = this.activeTab;
}
});
// 3. Sync URL -> State (Back/Forward buttons)
window.addEventListener('hashchange', () => {
const hash = getHash();
if (validTabs.includes(hash) && this.activeTab !== hash) {
this.activeTab = hash;
}
});
},
// App State
version: '1.0.0',
activeTab: 'dashboard',