feat: implement scenario caching and hot-reloading

Signed-off-by: Abhinav Sinha <[email protected]>
This commit is contained in:
Abhinav Sinha
2026-06-15 15:28:53 +05:30
parent 8489544981
commit ab2403e914
4 changed files with 140 additions and 3 deletions
+39
View File
@@ -13,6 +13,31 @@ export default function Header({ clusterReady }) {
const toggleTheme = () => setTheme(t => t === 'dark' ? 'light' : 'dark')
const handleReloadCache = async () => {
const sendReloadRequest = async () => {
return fetch('/api/cache/reload', {
method: 'POST',
headers: {
'Content-Type': 'application/json'
}
});
};
try {
let response = await sendReloadRequest();
if (response.ok) {
const data = await response.json();
alert(`Success: ${data.message}\nScenarios: ${data.scenarios_count}\nBundles: ${data.bundles_count}`);
window.location.reload();
} else {
const data = await response.json().catch(() => ({}));
alert(`Error reloading cache: ${data.error || 'Unknown error'}`);
}
} catch (err) {
alert(`Network error reloading cache: ${err.message}`);
}
};
return (
<header className={styles.header}>
<div className={styles.brand}>
@@ -54,6 +79,20 @@ export default function Header({ clusterReady }) {
<span className={styles.dot} />
<span>{clusterReady ? 'Cluster Ready' : 'Connecting…'}</span>
</div>
{/* Reload cache */}
<div className={styles.reloadBtnContainer}>
<button
className={styles.reloadBtn}
onClick={handleReloadCache}
aria-label="Reload scenario cache"
>
<svg viewBox="0 0 24 24" width="16" height="16" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round">
<path d="M23 4v6h-6" />
<path d="M20.49 15a9 9 0 1 1-2.12-9.36L23 10" />
</svg>
</button>
</div>
</div>
</header>
)
+52
View File
@@ -107,6 +107,58 @@
transform: rotate(12deg);
}
.reloadBtnContainer {
position: relative;
display: flex;
align-items: center;
}
.reloadBtnContainer::after {
content: "Reload Scenario Cache";
position: absolute;
top: calc(100% + 8px);
right: 0;
background: var(--surface3);
color: var(--text);
font-family: var(--mono);
font-size: 11px;
padding: 4px 8px;
border-radius: 4px;
border: 1px solid var(--border2);
white-space: nowrap;
opacity: 0;
pointer-events: none;
transition: opacity 0.1s ease;
z-index: 1000;
box-shadow: 0 4px 12px rgba(0, 0, 0, 0.35);
}
.reloadBtnContainer:hover::after {
opacity: 1;
}
.reloadBtn {
background: none;
border: 1px solid var(--border);
border-radius: 8px;
width: 34px;
height: 34px;
display: flex;
align-items: center;
justify-content: center;
font-size: 16px;
cursor: pointer;
transition: background 0.15s, border-color 0.15s, transform 0.2s, color 0.15s;
color: var(--text-2);
line-height: 1;
}
.reloadBtn:hover {
background: var(--surface2);
border-color: var(--border2);
color: var(--text);
transform: rotate(12deg);
}
.clusterBadge {
display: flex;
align-items: center;