Fix browser crash: cache schema context, lazy Dashboard, default to Ask tab

Root cause: Dashboard auto-fired 6+ API calls on load, then Ask tab fired
7 DESCRIBE queries per question — 15+ concurrent requests from WASM.

Fixes:
- Schema context cached after first build (7 DESCRIBE → 0 on subsequent questions)
- Dashboard lazy-loads only when tab clicked (not on app mount)
- Default tab changed back to Ask (no background API storm)
- std::sync::Mutex for WASM compat (no tokio in browser)

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
root 2026-03-27 21:18:53 -05:00
parent 238cb84d26
commit fdb2e9cda8

View File

@ -109,8 +109,30 @@ async fn fetch_health(path: &str) -> Result<String, String> {
resp.text().await.map_err(|e| e.to_string())
}
/// Cached schema context — built once, reused across questions.
static SCHEMA_CACHE: std::sync::OnceLock<std::sync::Mutex<Option<String>>> = std::sync::OnceLock::new();
async fn get_schema_context_cached(datasets: &[Dataset]) -> String {
// Check cache first
{
let cache = SCHEMA_CACHE.get_or_init(|| std::sync::Mutex::new(None));
if let Ok(guard) = cache.lock() {
if let Some(ref cached) = *guard {
return cached.clone();
}
}
}
// Build and cache
let ctx = get_schema_context(datasets).await;
if let Some(cache) = SCHEMA_CACHE.get() {
if let Ok(mut guard) = cache.lock() {
*guard = Some(ctx.clone());
}
}
ctx
}
/// Get schema context for datasets (used for AI SQL generation).
/// Limits to core tables to keep prompt size reasonable.
async fn get_schema_context(datasets: &[Dataset]) -> String {
let core_tables = ["candidates", "clients", "job_orders", "placements", "timesheets", "call_log", "email_log"];
let mut ctx = String::from("DATABASE SCHEMA:\n\n");
@ -163,7 +185,7 @@ enum Tab {
#[component]
fn App() -> Element {
let mut active_tab = use_signal(|| Tab::Dashboard);
let mut active_tab = use_signal(|| Tab::Ask);
let mut datasets = use_signal(Vec::<Dataset>::new);
let mut ds_loading = use_signal(|| true);
@ -268,7 +290,7 @@ fn AskPanel(datasets: Vec<Dataset>) -> Element {
// Step 1: Get schema context
step.set("reading schemas...".into());
let schema_ctx = get_schema_context(&ds).await;
let schema_ctx = get_schema_context_cached(&ds).await;
// Step 2: Generate SQL
step.set("writing SQL...".into());
@ -351,7 +373,7 @@ fn AskPanel(datasets: Vec<Dataset>) -> Element {
generated_sql.set(None);
result.set(None);
step.set("reading schemas...".into());
let schema_ctx = get_schema_context(&ds).await;
let schema_ctx = get_schema_context_cached(&ds).await;
step.set("writing SQL...".into());
let prompt = format!(
"You are a SQL assistant. You write Apache DataFusion SQL (PostgreSQL-compatible).\n\n\
@ -673,8 +695,14 @@ fn DashboardPanel() -> Element {
};
// Auto-load on mount
// Auto-load on mount
use_effect(move || { do_load(); });
// Load on first render (user clicks Dashboard tab)
let mut loaded = use_signal(|| false);
use_effect(move || {
if !*loaded.read() {
loaded.set(true);
do_load();
}
});
rsx! {
div { class: "panel",