add all files

This commit is contained in:
Rucus
2026-02-17 09:29:34 -06:00
parent b8c8d67c67
commit 782d203799
21925 changed files with 2433086 additions and 0 deletions

View File

@@ -0,0 +1,123 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>SugarScale SQL Agent</title>
<link rel="stylesheet" href="/static/styles.css" />
</head>
<body>
<header>
<h1>SugarScale SQL Agent</h1>
<p>Ask natural-language questions and execute the generated SQL.</p>
</header>
<main>
<div class="form-columns">
<section class="panel form-panel">
<form id="query-form">
<label for="question">Question</label>
<textarea id="question" name="question" rows="3" placeholder="e.g., How many loads were completed yesterday?" required></textarea>
<label for="tables">Table hints (comma-separated)</label>
<input id="tables" name="tables" type="text" placeholder="dbo.SugarLoadData" value="dbo.SugarLoadData" />
<div class="options">
<label>
<input type="checkbox" id="execute" name="execute" checked /> Execute SQL
</label>
<label for="max-rows">Max rows</label>
<input id="max-rows" name="max_rows" type="number" min="1" value="500" />
</div>
<div class="actions">
<button type="submit">Run</button>
<button type="button" id="reset-form" class="secondary">Reset</button>
</div>
</form>
</section>
<section class="panel helper-panel">
<label>Quick Reference</label>
<p class="helper-intro">Use these static values to include detailed data in your questions.</p>
<div class="helper-grid">
<div>
<label>Companies</label>
<ul>
<li>BigDawg</li>
<li>CSC</li>
<li>LACA</li>
<li>LASUCA</li>
<li>Ralph Callier</li>
</ul>
</div>
<div>
<label>Destinations</label>
<ul>
<li>LSR</li>
<li>CSC</li>
<li>Barge</li>
<li>Other</li>
</ul>
</div>
</div>
<p class="helper-intro">Dates can be represented as 10-12-25, 10/12/25, ect. Time can be exact or as simple as 6am, 4pm, ect.</p>
<div>
<label>Sample Questions</label>
<ul>
<li>List all loads from yesterday.</li>
<li>Show all loads hauled by LACA last week.</li>
<li>Show all loads hauled to barge this week.</li>
<li>Total tons hauled on 10/14/25.</li>
</ul>
</div>
</section>
</div>
<section class="panel results-panel" id="results" hidden>
<h2>Results</h2>
<div class="warning" id="warning" hidden></div>
<div id="summary"></div>
<details>
<summary>Generated SQL</summary>
<pre><code id="generated-sql"></code></pre>
</details>
<details id="sanitized-block" hidden>
<summary>Clean SQL (executed)</summary>
<pre><code id="sanitized-sql"></code></pre>
</details>
<div id="row-count"></div>
<div class="table-wrapper" id="table-wrapper" hidden>
<table id="result-table">
<thead id="result-thead"></thead>
<tbody id="result-tbody"></tbody>
</table>
</div>
<div class="export" id="export-wrapper" hidden>
<button type="button" id="export-results" class="secondary">Export CSV</button>
<button type="button" id="export-report" class="secondary">Download Report</button>
</div>
<div id="feedback-panel" hidden>
<h3>Was this answer helpful?</h3>
<div class="feedback-actions">
<button type="button" id="feedback-correct">Mark Correct</button>
<button type="button" id="feedback-incorrect" class="secondary">Needs Work</button>
</div>
<p id="feedback-status" hidden></p>
</div>
</section>
<section class="panel error" id="error" hidden>
<h2>Error</h2>
<pre id="error-message"></pre>
</section>
</main>
<footer>
<small>Powered by the SugarScale SQL agent.</small>
</footer>
<script src="/static/app.js" type="module"></script>
</body>
</html>

View File

@@ -0,0 +1,365 @@
const form = document.getElementById("query-form");
const resultsPanel = document.getElementById("results");
const errorPanel = document.getElementById("error");
const warningEl = document.getElementById("warning");
const summaryEl = document.getElementById("summary");
const generatedSqlEl = document.getElementById("generated-sql");
const sanitizedBlock = document.getElementById("sanitized-block");
const sanitizedSqlEl = document.getElementById("sanitized-sql");
const rowCountEl = document.getElementById("row-count");
const tableWrapper = document.getElementById("table-wrapper");
const tableEl = document.getElementById("result-table");
const tableHeadEl = document.getElementById("result-thead");
const tableBodyEl = document.getElementById("result-tbody");
const errorMessageEl = document.getElementById("error-message");
const feedbackPanel = document.getElementById("feedback-panel");
const feedbackCorrectBtn = document.getElementById("feedback-correct");
const feedbackIncorrectBtn = document.getElementById("feedback-incorrect");
const feedbackStatus = document.getElementById("feedback-status");
const resetBtn = document.getElementById("reset-form");
const exportWrapper = document.getElementById("export-wrapper");
const exportBtn = document.getElementById("export-results");
const reportBtn = document.getElementById("export-report");
let latestResult = null;
function resetPanels() {
resultsPanel.hidden = true;
errorPanel.hidden = true;
warningEl.hidden = true;
warningEl.textContent = "";
summaryEl.textContent = "";
generatedSqlEl.textContent = "";
sanitizedBlock.hidden = true;
sanitizedSqlEl.textContent = "";
rowCountEl.textContent = "";
tableEl.hidden = true;
tableWrapper.hidden = true;
tableHeadEl.innerHTML = "";
tableBodyEl.innerHTML = "";
errorMessageEl.textContent = "";
feedbackPanel.hidden = true;
feedbackStatus.hidden = true;
feedbackStatus.textContent = "";
exportWrapper.hidden = true;
exportBtn.disabled = true;
if (reportBtn) {
reportBtn.disabled = true;
}
latestResult = null;
}
function renderTable(columns, rows) {
if (!columns || !rows || rows.length === 0) {
tableEl.hidden = true;
tableWrapper.hidden = true;
return false;
}
tableHeadEl.innerHTML = "";
tableBodyEl.innerHTML = "";
const headerRow = document.createElement("tr");
columns.forEach((col) => {
const th = document.createElement("th");
th.textContent = col;
headerRow.appendChild(th);
});
tableHeadEl.appendChild(headerRow);
rows.forEach((row) => {
const tr = document.createElement("tr");
columns.forEach((col) => {
const td = document.createElement("td");
const value = row[col];
td.textContent = value === null || value === undefined ? "" : String(value);
tr.appendChild(td);
});
tableBodyEl.appendChild(tr);
});
tableEl.hidden = false;
tableWrapper.hidden = false;
return true;
}
function toCsv(columns, rows) {
const escapeCell = (value) => {
if (value === null || value === undefined) {
return "";
}
const stringValue = String(value);
if (/[",\n]/.test(stringValue)) {
return `"${stringValue.replace(/"/g, '""')}"`;
}
return stringValue;
};
const header = columns.map(escapeCell).join(",");
const lines = rows.map((row) => columns.map((col) => escapeCell(row[col])).join(","));
return [header, ...lines].join("\n");
}
function downloadCsv(filename, columns, rows) {
const csvContent = toCsv(columns, rows);
const blob = new Blob([csvContent], { type: "text/csv;charset=utf-8;" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
form.addEventListener("submit", async (event) => {
event.preventDefault();
resetPanels();
const question = document.getElementById("question").value.trim();
const tablesValue = document.getElementById("tables").value.trim();
const execute = document.getElementById("execute").checked;
const maxRows = parseInt(document.getElementById("max-rows").value, 10) || 500;
if (!question) {
errorPanel.hidden = false;
errorMessageEl.textContent = "Please enter a question.";
return;
}
const payload = {
question,
tables: tablesValue ? tablesValue.split(",").map((s) => s.trim()).filter(Boolean) : undefined,
execute,
max_rows: maxRows,
feedback: null,
};
try {
const response = await fetch("/api/query", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `Request failed with status ${response.status}`);
}
const data = await response.json();
summaryEl.textContent = data.summary || "(No summary provided)";
generatedSqlEl.textContent = data.sql || "";
if (data.llm_warning) {
warningEl.textContent = data.llm_warning;
warningEl.hidden = false;
}
if (data.sanitized_sql) {
sanitizedSqlEl.textContent = data.sanitized_sql;
sanitizedBlock.hidden = false;
}
if (typeof data.row_count === "number") {
rowCountEl.textContent = `Rows returned: ${data.row_count}`;
}
const hasRows = renderTable(data.columns, data.rows);
latestResult = {
question,
sql: data.sql || "",
summary: data.summary || "",
sanitized_sql: data.sanitized_sql || null,
columns: Array.isArray(data.columns) ? data.columns : [],
rows: Array.isArray(data.rows) ? data.rows : [],
warning: data.llm_warning || null,
rowCount: typeof data.row_count === "number" ? data.row_count : null,
};
feedbackPanel.hidden = false;
feedbackStatus.hidden = true;
feedbackStatus.textContent = "";
const canExportTable = hasRows && latestResult.rows.length > 0;
if (reportBtn) {
exportWrapper.hidden = false;
reportBtn.disabled = false;
exportBtn.disabled = !canExportTable;
} else if (canExportTable) {
exportWrapper.hidden = false;
exportBtn.disabled = false;
}
resultsPanel.hidden = false;
} catch (error) {
errorPanel.hidden = false;
errorMessageEl.textContent = error.message || String(error);
}
});
resetBtn.addEventListener("click", () => {
form.reset();
document.getElementById("tables").value = document.getElementById("tables").defaultValue;
document.getElementById("max-rows").value = document.getElementById("max-rows").defaultValue;
document.getElementById("execute").checked = document.getElementById("execute").defaultChecked;
resetPanels();
});
exportBtn.addEventListener("click", () => {
if (!latestResult || !latestResult.columns || latestResult.rows.length === 0) {
return;
}
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const filename = `sql-agent-results-${timestamp}.csv`;
downloadCsv(filename, latestResult.columns, latestResult.rows);
});
function escapeHtml(value) {
if (value === null || value === undefined) {
return "";
}
return String(value)
.replace(/&/g, "&amp;")
.replace(/</g, "&lt;")
.replace(/>/g, "&gt;")
.replace(/"/g, "&quot;")
.replace(/'/g, "&#39;");
}
function buildReportHtml(result) {
const timestamp = new Date().toLocaleString();
const rowsHtml = result.rows
.map(
(row) =>
`<tr>${result.columns
.map((col) => `<td>${escapeHtml(row[col])}</td>`)
.join("")}</tr>`
)
.join("");
const tableSection =
result.columns.length && result.rows.length
? `<table><thead><tr>${result.columns
.map((col) => `<th>${escapeHtml(col)}</th>`)
.join("")}</tr></thead><tbody>${rowsHtml}</tbody></table>`
: `<p>No row data was returned.</p>`;
const sanitizedSection = result.sanitized_sql
? `<h3>Sanitized SQL</h3><pre>${escapeHtml(result.sanitized_sql)}</pre>`
: "";
const warningSection = result.warning
? `<div class="warning">${escapeHtml(result.warning)}</div>`
: "";
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<title>SQL Agent Report</title>
<style>
body { font-family: 'Segoe UI', sans-serif; margin: 2rem auto; max-width: 960px; color: #0f172a; }
h1, h2, h3 { color: #1e3a8a; }
.meta { margin-bottom: 1.5rem; }
.meta dt { font-weight: 600; }
.meta dd { margin: 0 0 0.75rem 0; }
pre { background: #0f172a; color: #f8fafc; padding: 1rem; border-radius: 8px; overflow-x: auto; }
table { width: 100%; border-collapse: collapse; margin-top: 1.5rem; }
th, td { border: 1px solid #e2e8f0; padding: 0.75rem; text-align: left; }
th { background: #e2e8f0; }
.warning { background: #fef3c7; border-left: 4px solid #f59e0b; padding: 0.75rem 1rem; border-radius: 8px; margin-bottom: 1.5rem; }
footer { margin-top: 2rem; font-size: 0.85rem; color: #475569; }
</style>
</head>
<body>
<h1>SQL Agent Report</h1>
<dl class="meta">
<dt>Generated</dt>
<dd>${escapeHtml(timestamp)}</dd>
<dt>Question</dt>
<dd>${escapeHtml(result.question)}</dd>
<dt>Summary</dt>
<dd>${escapeHtml(result.summary || '(No summary provided)')}</dd>
<dt>Rows Returned</dt>
<dd>${result.rowCount !== null ? escapeHtml(result.rowCount) : 'n/a'}</dd>
</dl>
${warningSection}
<h2>Generated SQL</h2>
<pre>${escapeHtml(result.sql)}</pre>
${sanitizedSection}
<h2>Result Preview</h2>
${tableSection}
<footer>Report generated by the SugarScale SQL Agent UI.</footer>
</body>
</html>`;
}
function downloadReport(filename, html) {
const blob = new Blob([html], { type: "text/html;charset=utf-8;" });
const url = URL.createObjectURL(blob);
const link = document.createElement("a");
link.href = url;
link.download = filename;
document.body.appendChild(link);
link.click();
document.body.removeChild(link);
URL.revokeObjectURL(url);
}
if (reportBtn) {
reportBtn.addEventListener("click", () => {
if (!latestResult) {
return;
}
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
const filename = `sql-agent-report-${timestamp}.html`;
const html = buildReportHtml(latestResult);
downloadReport(filename, html);
});
}
async function sendFeedback(tag) {
if (!latestResult) {
return;
}
const payload = {
question: latestResult.question,
sql: latestResult.sql,
summary: latestResult.summary,
sanitized_sql: latestResult.sanitized_sql,
feedback: tag,
};
feedbackStatus.hidden = false;
feedbackStatus.textContent = "Sending feedback...";
feedbackCorrectBtn.disabled = true;
feedbackIncorrectBtn.disabled = true;
try {
const response = await fetch("/api/feedback", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(payload),
});
if (!response.ok) {
const errorData = await response.json().catch(() => ({}));
throw new Error(errorData.detail || `Feedback request failed with status ${response.status}`);
}
feedbackStatus.textContent = tag === "correct" ? "Marked as correct." : "Marked as needs work.";
} catch (error) {
feedbackStatus.textContent = error.message || String(error);
} finally {
feedbackCorrectBtn.disabled = false;
feedbackIncorrectBtn.disabled = false;
}
}
feedbackCorrectBtn.addEventListener("click", () => sendFeedback("correct"));
feedbackIncorrectBtn.addEventListener("click", () => sendFeedback("incorrect"));

View File

@@ -0,0 +1,218 @@
* {
box-sizing: border-box;
}
body {
font-family: system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
margin: 0;
padding: 0;
background: #f4f6f8;
color: #0f172a;
}
header,
footer {
background: #0f172a;
color: #f8fafc;
padding: 1.5rem;
text-align: center;
}
main {
display: grid;
gap: 1.5rem;
max-width: 1000px;
margin: 2rem auto;
padding: 0 1rem;
}
.form-columns {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
gap: 1.5rem;
align-items: start;
}
.panel {
background: #ffffff;
padding: 1.5rem;
border-radius: 12px;
box-shadow: 0 12px 24px rgba(15, 23, 42, 0.08);
}
.panel.error {
border-left: 6px solid #ef4444;
}
.form-panel,
.helper-panel {
width: 100%;
max-width: 420px;
justify-self: center;
}
.helper-panel h3 {
margin-top: 0rem;
font-size: 1.05rem;
}
.helper-intro,
.helper-note {
color: #475569;
font-size: 0.95rem;
}
.helper-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(160px, 1fr));
gap: 1rem;
}
.helper-panel ul {
margin: 0.25rem 0 0;
padding-left: 1.25rem;
}
.results-panel {
width: 100%;
max-height: calc(100vh - 8rem);
overflow-y: auto;
}
.warning {
background: #fef3c7;
color: #92400e;
border-left: 4px solid #f59e0b;
padding: 0.75rem 1rem;
border-radius: 8px;
margin-bottom: 1rem;
white-space: pre-wrap;
}
label {
display: block;
font-weight: 600;
margin-bottom: 0.35rem;
}
textarea,
input,
button {
width: 100%;
padding: 0.75rem;
border-radius: 8px;
border: 1px solid #cbd5f5;
font-size: 1rem;
margin-bottom: 1rem;
}
textarea:focus,
input:focus {
outline: none;
border-color: #2563eb;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.2);
}
.options {
display: flex;
gap: 1rem;
align-items: center;
}
.options label {
margin-bottom: 0;
font-weight: 500;
}
.actions {
display: flex;
gap: 1rem;
}
.actions button {
margin-bottom: 0;
width: auto;
flex: 1 1 auto;
}
.export {
margin-top: 1rem;
display: flex;
gap: 0.75rem;
flex-wrap: wrap;
}
.export button {
width: auto;
}
button {
background: linear-gradient(135deg, #2563eb, #1d4ed8);
color: #fff;
border: none;
cursor: pointer;
font-weight: 600;
transition: transform 0.15s ease, box-shadow 0.15s ease;
}
button:hover {
transform: translateY(-1px);
box-shadow: 0 10px 20px rgba(37, 99, 235, 0.25);
}
button.secondary {
background: linear-gradient(135deg, #64748b, #475569);
}
pre {
background: #0f172a;
color: #e2e8f0;
padding: 1rem;
border-radius: 8px;
overflow-x: auto;
}
.results-panel .table-wrapper {
overflow-x: auto;
margin-top: 1rem;
}
.results-panel .table-wrapper table {
width: max-content;
min-width: 100%;
}
table {
border-collapse: collapse;
margin: 0;
}
th,
td {
border: 1px solid #e2e8f0;
padding: 0.65rem;
text-align: left;
}
th {
background: #f1f5f9;
}
#error-message {
white-space: pre-wrap;
}
#feedback-panel {
margin-top: 1.5rem;
}
.feedback-actions {
display: flex;
gap: 1rem;
margin-bottom: 0.75rem;
}
#feedback-status {
color: #0369a1;
font-weight: 600;
}