Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
09a4cf2
spec: csv-analyst full spec (roadmap/architecture/agent/data/api/ui/c…
madhyamakist Jul 20, 2026
d82badb
harness v0.3.1: optimize the build loop for step-3.7-class models (ga…
madhyamakist Jul 20, 2026
5af3de6
harness v0.3.1 addendum: nvidia-locked refinements from 25 more live …
madhyamakist Jul 20, 2026
398b631
harness: merge v0.3.1 step-3.7 optimization (gates over prose, contex…
madhyamakist Jul 20, 2026
183a683
phase-1 wip
madhyamakist Jul 20, 2026
5d91254
harness v0.3.1: provider-hiccup protocol — repo-only resilience, no H…
madhyamakist Jul 20, 2026
5f9281d
Merge remote-tracking branch 'origin/harness/v0.3.1-step37-optimizati…
madhyamakist Jul 20, 2026
a83d245
harness v0.3.1: de-meta pass — SKILL carries instructions, not war st…
madhyamakist Jul 20, 2026
9c88240
Merge remote-tracking branch 'origin/harness/v0.3.1-step37-optimizati…
madhyamakist Jul 20, 2026
d50ca6c
harness v0.3.1: 429 cool-down reflex — sleep 30 + batch harder after …
madhyamakist Jul 20, 2026
4bb14cc
Merge remote-tracking branch 'origin/harness/v0.3.1-step37-optimizati…
madhyamakist Jul 20, 2026
20fe4a0
restore main (v0.3) harness for step-3.7 A/B — revert v0.3.1 docs on …
madhyamakist Jul 20, 2026
8c3142f
harness v0.3.2: micro-trim over main — request-budget pacing + budget…
madhyamakist Jul 20, 2026
49c2c50
Merge remote-tracking branch 'origin/harness/v0.3.2-micro-trim' into …
madhyamakist Jul 20, 2026
e63f69c
harness v0.3.2: pacing floor — sleep 15 before every terminal command…
madhyamakist Jul 20, 2026
184b1ff
Merge remote-tracking branch 'origin/harness/v0.3.2-micro-trim' into …
madhyamakist Jul 20, 2026
ba8072e
harness v0.3.2: anti-thrash + narration rules — nemotron-class behavi…
madhyamakist Jul 20, 2026
0a6e2cc
Merge remote-tracking branch 'origin/harness/v0.3.2-micro-trim' into …
madhyamakist Jul 20, 2026
051eacc
restore main (v0.3) harness on the build branch — pure-main config fo…
madhyamakist Jul 20, 2026
099712b
phase-1 wip: snapshot of test fixes + service edits before final step…
madhyamakist Jul 20, 2026
b46b438
phase-1: tests green — fix DuckDB CSV type inference for SUM/aggregation
madhyamakist Jul 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
193 changes: 163 additions & 30 deletions frontend/public/app.js
Original file line number Diff line number Diff line change
@@ -1,9 +1,13 @@
// Zero-build baseline frontend. Single-origin: the page is served by the
// backend at /app, so API calls are same-origin relative paths.

"use strict";

const $ = (id) => document.getElementById(id);

let sessionId = null;

// Load health badge
async function loadHealth() {
const badge = $("provider-badge");
try {
Expand All @@ -22,56 +26,185 @@ async function loadHealth() {
}
}

async function runTransform() {
// Create a session and return its ID
async function createSession() {
const res = await fetch("/sessions", { method: "POST" });
if (!res.ok) throw new Error(`Failed to create session: ${res.status}`);
const data = await res.json();
return data.data.id;
}

// Upload CSV files to the session
async function uploadCsvs(sessionId, files) {
const form = new FormData();
for (const file of files) {
form.append("files", file); // Note: changed from "file" to "files" to match backend
}
const res = await fetch(`/sessions/${sessionId}/csv`, {
method: "POST",
body: form,
});
if (!res.ok) throw new Error(`Failed to upload CSV: ${res.status}`);
return await res.json();
}

// Run the agent with a question and session ID
async function runAnalysis(sessionId, question) {
const res = await fetch("/runs", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({ session_id: sessionId, question }),
});
if (!res.ok) throw new Error(`Failed to run analysis: ${res.status}`);
return await res.json();
}

// Display the plan in the UI
function displayPlan(planText) {
$("plan").textContent = planText || "(no plan)";
}

// Display the generated SQL/code
function displayGeneratedCode(codeText) {
$("generated-code").textContent = codeText || "(no code)";
}

// Display the results in a table
function displayResults(rows) {
const table = $("result-table");
const thead = table.tHead || table.createTHead();
const tbody = table.tBody || table.createTBody();

// Clear existing content
thead.innerHTML = "";
tbody.innerHTML = "";

if (!rows || rows.length === 0) {
tbody.innerHTML = "<tr><td colspan='1'>No data</td></tr>";
return;
}

// Create header from the keys of the first row
const headers = Object.keys(rows[0]);
const headerRow = thead.insertRow();
headers.forEach((header) => {
const th = document.createElement("th");
th.textContent = header;
headerRow.appendChild(th);
});

// Create rows
rows.forEach((row) => {
const tr = tbody.insertRow();
headers.forEach((key) => {
const td = tr.insertCell();
td.textContent = row[key] !== null && row[key] !== undefined ? row[key] : "";
});
});
}

// Update KPIs (placeholder implementation)
function updateKpis(rows) {
// For simplicity, we'll show row count and placeholder for other KPIs
$("kpi-rows").textContent = rows ? rows.length : 0;
// In a real app, we would compute date range and distinct columns from the schema
$("kpi-date-range").textContent = "N/A";
$("kpi-categories").textContent = "N/A";
}

// Update chart placeholders (placeholder implementation)
function updateCharts() {
$("chart-bar").textContent = "[chart]";
$("chart-line").textContent = "[chart]";
$("chart-pie").textContent = "[chart]";
}

// Main function to handle the Analyze button click
async function runAnalysisClicked() {
const btn = $("run-btn");
const status = $("status");
const errBox = $("error");
const errorBox = $("error");
const wrap = $("result-wrap");

const text = $("text").value.trim();
const instruction = $("instruction").value.trim();

errBox.hidden = true;
wrap.hidden = true;
// Get files and question
const fileInput = $("csv-files");
const questionInput = $("question");
const files = fileInput.files;
const question = questionInput.value.trim();

if (!text) {
errBox.textContent = "Paste some text first — the input can't be empty.";
errBox.hidden = false;
// Basic validation
if (files.length === 0) {
errorBox.textContent = "Please select at least one CSV file.";
errorBox.hidden = false;
return;
}
if (!question) {
errorBox.textContent = "Please enter a question.";
errorBox.hidden = false;
return;
}

// Disable UI and clear previous results
btn.disabled = true;
status.textContent = "Running… (one real LLM call)";
status.textContent = "Analyzing...";
status.hidden = false;
errorBox.hidden = true;
wrap.hidden = true;

try {
const res = await fetch("/runs", {
method: "POST",
headers: { "content-type": "application/json" },
body: JSON.stringify({ text, instruction }),
});
const body = await res.json();
// Step 1: Create or reuse session
if (!sessionId) {
sessionId = await createSession();
}

// Step 2: Upload CSV files
await uploadCsvs(sessionId, files);

if (!res.ok) {
const msg = body?.detail?.message || `HTTP ${res.status}`;
throw new Error(msg);
// Step 3: Run analysis
const response = await runAnalysis(sessionId, question);
const data = response.data;

if (data.status === "failed") {
throw new Error(data.error_message || "Analysis failed");
}
const run = body.data;
if (run.status === "failed") {
throw new Error(run.error_message || "The agent run failed.");

// Step 4: Update UI with results
displayPlan(data.plan_text || "");
displayGeneratedCode(data.generated_code || "");

// For simplicity, we assume the result is in the output_text as a string.
// In a real implementation, we would parse the structured result from the agent.
// Here, we'll just show the output_text as a placeholder for the results.
// We'll also try to parse a JSON array from the output_text for the table.
let rows = [];
try {
// Try to parse the output_text as JSON (if it's a JSON array)
const parsed = JSON.parse(data.output_text);
if (Array.isArray(parsed)) {
rows = parsed;
}
} catch (e) {
// If not JSON, we'll just show the output_text in a single cell table
rows = [{ output: data.output_text }];
}
$("result").textContent = run.output_text;
$("result-meta").textContent =
`run ${run.run_id} · ${run.provider} · ${run.model}`;

displayResults(rows);
updateKpis(rows);
updateCharts();

// Show the results wrapper
wrap.hidden = false;
} catch (err) {
errBox.textContent = err.message;
errBox.hidden = false;
errorBox.textContent = err.message;
errorBox.hidden = false;
} finally {
btn.disabled = false;
status.hidden = true;
}
}

$("run-btn").addEventListener("click", runTransform);
loadHealth();
// Event listeners
document.addEventListener("DOMContentLoaded", () => {
loadHealth();
$("run-btn").addEventListener("click", runAnalysisClicked);
});
62 changes: 44 additions & 18 deletions frontend/public/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,65 @@
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Zero-Shot Agent — Transform</title>
<title>CSV Analyst Agent</title>
<link rel="stylesheet" href="styles.css" />
</head>
<body>
<main class="shell">
<header class="topbar">
<h1>Zero-Shot Agent</h1>
<h1>CSV Analyst Agent</h1>
<span id="provider-badge" class="badge" title="Active LLM provider">…</span>
</header>

<!-- CAPABILITY SLOT — replace this form with your agent's UI -->
<section class="card" id="transform-card">
<h2>Transform text</h2>
<p class="hint">The baseline capability: give it text and an instruction; it applies the
instruction with one real LLM call.</p>
<!-- CAPABILITY SLOT -- replace this form with your agent's UI -->
<section class="card" id="analyst-card">
<h2>CSV Analyst</h2>
<p class="hint">
Upload CSV files and ask questions in plain English. The agent will
inspect the data, generate a SQL query, and return the answer with a
table, charts, and key metrics.
</p>

<label for="instruction">Instruction</label>
<input id="instruction" type="text"
value="Summarize the text in one short paragraph." />
<label for="csv-files">CSV Files</label>
<input id="csv-files" type="file" multiple accept=".csv" />

<label for="text">Text</label>
<textarea id="text" rows="8"
placeholder="Paste the text to transform…"></textarea>
<label for="question">Question</label>
<input id="question" type="text"
placeholder="e.g., Show me the top 10 police stations by FIR count" />

<button id="run-btn">Run</button>
<button id="run-btn">Analyze</button>

<div id="status" class="status" hidden></div>
<div id="error" class="error" hidden></div>

<div id="result-wrap" hidden>
<h3>Result</h3>
<pre id="result"></pre>
<p class="meta" id="result-meta"></p>
<h3>Plan</h3>
<pre id="plan"></pre>

<h3>Generated SQL</h3>
<pre id="generated-code"></pre>

<h3>Results</h3>
<div id="results-container">
<table id="result-table">
<thead></thead>
<tbody></tbody>
</table>
</div>

<h3>Charts</h3>
<div id="charts">
<div>Bar Chart: <span id="chart-bar">[placeholder]</span></div>
<div>Line Chart: <span id="chart-line">[placeholder]</span></div>
<div>Pie Chart: <span id="chart-pie">[placeholder]</span></div>
</div>

<h3>Key Metrics</h3>
<div id="kpis">
<div>Total Rows: <span id="kpi-rows">0</span></div>
<div>Date Range: <span id="kpi-date-range">N/A</span></div>
<div>Distinct Categories: <span id="kpi-categories">0</span></div>
</div>
</div>
</section>

Expand All @@ -45,4 +71,4 @@ <h3>Result</h3>
</main>
<script src="app.js"></script>
</body>
</html>
</html>
50 changes: 44 additions & 6 deletions frontend/public/styles.css
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ body {
color: var(--text);
line-height: 1.5;
}
.shell { max-width: 760px; margin: 0 auto; padding: 24px 16px 48px; }
.shell { max-width: 960px; margin: 0 auto; padding: 24px 16px 48px; }
.topbar { display: flex; align-items: center; justify-content: space-between; }
.topbar h1 { font-size: 1.4rem; margin: 0; }
.badge {
Expand All @@ -33,12 +33,16 @@ body {
.card h2 { margin-top: 0; font-size: 1.1rem; }
.hint { color: var(--muted); font-size: 0.9rem; margin-top: -6px; }
label { display: block; margin: 14px 0 4px; font-size: 0.85rem; color: var(--muted); }
input[type="text"], textarea {
input[type="file"],
input[type="text"],
textarea {
width: 100%; padding: 10px 12px; border-radius: 8px;
border: 1px solid #2b3245; background: #10131a; color: var(--text);
font: inherit; resize: vertical;
}
input:focus, textarea:focus { outline: 2px solid var(--accent); border-color: transparent; }
input[type="file"]:focus,
input[type="text"]:focus,
textarea:focus { outline: 2px solid var(--accent); border-color: transparent; }
button {
margin-top: 16px; padding: 10px 22px; border: 0; border-radius: 8px;
background: var(--accent); color: #fff; font: inherit; font-weight: 600; cursor: pointer;
Expand All @@ -50,10 +54,44 @@ button:disabled { opacity: 0.55; cursor: wait; }
background: #2c1a19; border: 1px solid #5a2c29; color: var(--error); font-size: 0.9rem;
}
#result-wrap h3 { margin: 18px 0 6px; font-size: 0.95rem; }
pre#result {
pre {
background: #10131a; border: 1px solid #2b3245; border-radius: 8px;
padding: 14px; white-space: pre-wrap; word-wrap: break-word; margin: 0;
padding: 14px; white-space: pre-wrap; word-wrap: break-word; margin: 0 0 10px 0;
}
#results-container {
margin: 10px 0;
overflow-x: auto;
}
table {
width: 100%;
border-collapse: collapse;
margin: 10px 0;
}
th, td {
padding: 8px 12px;
border: 1px solid #2b3245;
text-align: left;
}
thead {
background: #1a1d24;
}
tbody tr:nth-child(even) {
background: #10131a;
}
#charts, #kpis {
display: flex;
flex-wrap: wrap;
gap: 20px;
margin: 10px 0;
}
#charts > div, #kpis > div {
background: #10131a;
border: 1px solid #2b3245;
border-radius: 8px;
padding: 10px 14px;
flex: 1;
min-width: 150px;
}
.meta { color: var(--muted); font-size: 0.78rem; }
.foot { margin-top: 28px; color: var(--muted); font-size: 0.85rem; text-align: center; }
.foot a { color: var(--accent); }
.foot a { color: var(--accent); }
Loading