Spaces:
Sleeping
Sleeping
Upload index.html
Browse files- static/index.html +215 -2
static/index.html
CHANGED
|
@@ -686,6 +686,28 @@
|
|
| 686 |
<!-- MAIN CONTENT -->
|
| 687 |
<main class="main">
|
| 688 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 689 |
<!-- HOW TO USE SECTION -->
|
| 690 |
<div class="help-section" id="help-section">
|
| 691 |
<div class="help-title">
|
|
@@ -964,6 +986,13 @@ let state = {
|
|
| 964 |
schemaInfo: null,
|
| 965 |
};
|
| 966 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 967 |
// βββ TASK DEFINITIONS βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 968 |
const TASKS = {
|
| 969 |
null_audit: {
|
|
@@ -1143,6 +1172,46 @@ async function resetEpisode() {
|
|
| 1143 |
|
| 1144 |
// βββ QUERY ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1145 |
async function runQuery() {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1146 |
if (!state.currentTask) { toast('No active task β initialize first', 'error'); return; }
|
| 1147 |
const sql = document.getElementById('query-sql').value.trim();
|
| 1148 |
if (!sql) { toast('Enter a SQL statement', 'error'); return; }
|
|
@@ -1199,7 +1268,8 @@ async function runQuery() {
|
|
| 1199 |
}
|
| 1200 |
}
|
| 1201 |
} else {
|
| 1202 |
-
output.innerHTML = '<div class="output-error">Error: ' + escapeHtml(r.data.detail || 'Unknown error') +
|
|
|
|
| 1203 |
}
|
| 1204 |
setLoading('query', false);
|
| 1205 |
}
|
|
@@ -1282,6 +1352,7 @@ function verifyFix() {
|
|
| 1282 |
// βββ SUBMIT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1283 |
async function submitEpisode() {
|
| 1284 |
if (!state.currentTask) { toast('No active task', 'error'); return; }
|
|
|
|
| 1285 |
|
| 1286 |
const task = TASKS[state.currentTask];
|
| 1287 |
const body = {
|
|
@@ -1340,6 +1411,144 @@ async function submitEpisode() {
|
|
| 1340 |
setLoading('submit', false);
|
| 1341 |
}
|
| 1342 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1343 |
// βββ SCHEMA RENDERING βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1344 |
function parseColumn(col) {
|
| 1345 |
// col is either a string "email (TEXT)" or an object {name, type, nullable}
|
|
@@ -1392,7 +1601,11 @@ function renderSchema(schemaInfo) {
|
|
| 1392 |
html += '<tr><td class="col-name">' + escapeHtml(parsed.name) + '</td>';
|
| 1393 |
html += '<td class="col-type"><span class="' + typeClass + '">' + escapeHtml(parsed.type) + '</span></td></tr>';
|
| 1394 |
}
|
| 1395 |
-
html += '</tbody></table>
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1396 |
}
|
| 1397 |
panel.innerHTML = html;
|
| 1398 |
}
|
|
|
|
| 686 |
<!-- MAIN CONTENT -->
|
| 687 |
<main class="main">
|
| 688 |
|
| 689 |
+
<!-- βββ UPLOAD SECTION ββββββββββββββββββββββββββββββββββββββββββββββββ -->
|
| 690 |
+
<div class="help-section" style="background: linear-gradient(135deg, #E3F2FD 0%, #F3E5F5 100%); border-color: #90CAF9; margin-bottom: 16px;" id="upload-section">
|
| 691 |
+
<div class="help-title" style="color: #1565C0;">
|
| 692 |
+
<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
|
| 693 |
+
<path d="M21 15v4a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2v-4"/>
|
| 694 |
+
<polyline points="17 8 12 3 7 8"/>
|
| 695 |
+
<line x1="12" y1="3" x2="12" y2="15"/>
|
| 696 |
+
</svg>
|
| 697 |
+
Custom Database Upload
|
| 698 |
+
</div>
|
| 699 |
+
<div style="display: flex; gap: 12px; align-items: center; flex-wrap: wrap;">
|
| 700 |
+
<span style="font-size: 13px; color: var(--text-secondary);">Or use your own database:</span>
|
| 701 |
+
<input type="file" id="db-file-input" accept=".sqlite,.db,.csv" style="font-size: 13px; padding: 6px; border: 1px solid var(--card-border); border-radius: var(--radius);">
|
| 702 |
+
<button class="btn btn-primary" onclick="uploadDatabase()" id="upload-btn">
|
| 703 |
+
<span class="spinner" id="upload-spinner"></span>
|
| 704 |
+
<span id="upload-btn-text">Upload</span>
|
| 705 |
+
</button>
|
| 706 |
+
<span id="upload-status" style="font-size: 12px; color: var(--text-secondary);">No file loaded</span>
|
| 707 |
+
<button class="btn btn-secondary" onclick="backToTasks()" id="back-to-tasks-btn" style="display: none;">Back to Tasks</button>
|
| 708 |
+
</div>
|
| 709 |
+
<div id="upload-error" class="output-error" style="display: none; margin-top: 12px;"></div>
|
| 710 |
+
</div>
|
| 711 |
<!-- HOW TO USE SECTION -->
|
| 712 |
<div class="help-section" id="help-section">
|
| 713 |
<div class="help-title">
|
|
|
|
| 986 |
schemaInfo: null,
|
| 987 |
};
|
| 988 |
|
| 989 |
+
// βββ MODE βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 990 |
+
let mode = 'task'; // 'task' or 'custom'
|
| 991 |
+
let customDbLoaded = false;
|
| 992 |
+
let customDbSchema = [];
|
| 993 |
+
let customDbFilename = '';
|
| 994 |
+
let firstCustomTable = '';
|
| 995 |
+
|
| 996 |
// βββ TASK DEFINITIONS βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 997 |
const TASKS = {
|
| 998 |
null_audit: {
|
|
|
|
| 1172 |
|
| 1173 |
// βββ QUERY ββββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1174 |
async function runQuery() {
|
| 1175 |
+
if (mode === 'custom') {
|
| 1176 |
+
// Custom DB mode - use /query-custom endpoint
|
| 1177 |
+
const sql = document.getElementById('query-sql').value.trim();
|
| 1178 |
+
if (!sql) { toast('Enter a SQL statement', 'error'); return; }
|
| 1179 |
+
|
| 1180 |
+
setLoading('query', true);
|
| 1181 |
+
const container = document.getElementById('query-output-container');
|
| 1182 |
+
const output = document.getElementById('query-output');
|
| 1183 |
+
container.style.display = 'block';
|
| 1184 |
+
|
| 1185 |
+
try {
|
| 1186 |
+
const r = await api('POST', '/query-custom', { sql: sql });
|
| 1187 |
+
|
| 1188 |
+
if (r.ok) {
|
| 1189 |
+
if (r.data.error) {
|
| 1190 |
+
// SQL error with message
|
| 1191 |
+
output.innerHTML = '<div class="output-error">' + escapeHtml(r.data.error) +
|
| 1192 |
+
'<div class="error-tip">Tip: ' + escapeHtml(r.data.message || 'Check your query') + '</div></div>';
|
| 1193 |
+
} else if (r.data.rows) {
|
| 1194 |
+
// Success - format as table
|
| 1195 |
+
output.innerHTML = formatArrayAsTable(r.data.rows);
|
| 1196 |
+
if (r.data.row_count > 0) {
|
| 1197 |
+
output.innerHTML = '<div class="result-summary"><strong>' + r.data.row_count + '</strong> rows returned</div>' + output.innerHTML;
|
| 1198 |
+
}
|
| 1199 |
+
} else {
|
| 1200 |
+
output.innerHTML = '<div class="output-body empty">' + escapeHtml(r.data.message || 'No data') + '</div>';
|
| 1201 |
+
}
|
| 1202 |
+
} else {
|
| 1203 |
+
output.innerHTML = '<div class="output-error">Error: ' + escapeHtml(r.data.detail || 'Failed to execute query') +
|
| 1204 |
+
'<div class="error-tip">Tip: Only SELECT queries are allowed. Check table names in schema panel.</div></div>';
|
| 1205 |
+
}
|
| 1206 |
+
} catch (e) {
|
| 1207 |
+
output.innerHTML = '<div class="output-error">Error: ' + escapeHtml(e.message) + '</div>';
|
| 1208 |
+
}
|
| 1209 |
+
|
| 1210 |
+
setLoading('query', false);
|
| 1211 |
+
return;
|
| 1212 |
+
}
|
| 1213 |
+
|
| 1214 |
+
// RL Task mode - use /step endpoint
|
| 1215 |
if (!state.currentTask) { toast('No active task β initialize first', 'error'); return; }
|
| 1216 |
const sql = document.getElementById('query-sql').value.trim();
|
| 1217 |
if (!sql) { toast('Enter a SQL statement', 'error'); return; }
|
|
|
|
| 1268 |
}
|
| 1269 |
}
|
| 1270 |
} else {
|
| 1271 |
+
output.innerHTML = '<div class="output-error">Error: ' + escapeHtml(r.data.detail || 'Unknown error') +
|
| 1272 |
+
'<div class="error-tip">Tip: Check your SQL syntax and table names.</div></div>';
|
| 1273 |
}
|
| 1274 |
setLoading('query', false);
|
| 1275 |
}
|
|
|
|
| 1352 |
// βββ SUBMIT βββββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1353 |
async function submitEpisode() {
|
| 1354 |
if (!state.currentTask) { toast('No active task', 'error'); return; }
|
| 1355 |
+
if (mode === 'custom') { toast('Submit not available in custom DB mode', 'error'); return; }
|
| 1356 |
|
| 1357 |
const task = TASKS[state.currentTask];
|
| 1358 |
const body = {
|
|
|
|
| 1411 |
setLoading('submit', false);
|
| 1412 |
}
|
| 1413 |
|
| 1414 |
+
// βββ CUSTOM DB UPLOAD βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1415 |
+
async function uploadDatabase() {
|
| 1416 |
+
const fileInput = document.getElementById('db-file-input');
|
| 1417 |
+
const file = fileInput.files[0];
|
| 1418 |
+
|
| 1419 |
+
if (!file) {
|
| 1420 |
+
toast('Please select a file first', 'error');
|
| 1421 |
+
return;
|
| 1422 |
+
}
|
| 1423 |
+
|
| 1424 |
+
const validExts = ['.sqlite', '.db', '.csv'];
|
| 1425 |
+
const ext = file.name.slice(file.name.lastIndexOf('.')).toLowerCase();
|
| 1426 |
+
if (!validExts.includes(ext)) {
|
| 1427 |
+
document.getElementById('upload-error').style.display = 'block';
|
| 1428 |
+
document.getElementById('upload-error').innerHTML = 'Invalid file type. Must be .sqlite, .db, or .csv';
|
| 1429 |
+
return;
|
| 1430 |
+
}
|
| 1431 |
+
|
| 1432 |
+
setLoading('upload', true);
|
| 1433 |
+
document.getElementById('upload-error').style.display = 'none';
|
| 1434 |
+
|
| 1435 |
+
const formData = new FormData();
|
| 1436 |
+
formData.append('file', file);
|
| 1437 |
+
|
| 1438 |
+
try {
|
| 1439 |
+
const res = await fetch(BASE + '/upload-db', {
|
| 1440 |
+
method: 'POST',
|
| 1441 |
+
body: formData
|
| 1442 |
+
});
|
| 1443 |
+
|
| 1444 |
+
const data = await res.json();
|
| 1445 |
+
|
| 1446 |
+
if (res.ok && data.status === 'ok') {
|
| 1447 |
+
mode = 'custom';
|
| 1448 |
+
customDbLoaded = true;
|
| 1449 |
+
customDbSchema = data.tables;
|
| 1450 |
+
customDbFilename = data.filename;
|
| 1451 |
+
firstCustomTable = data.tables[0]?.table_name || 'table';
|
| 1452 |
+
|
| 1453 |
+
// Update UI
|
| 1454 |
+
document.getElementById('upload-status').innerHTML = 'β ' + data.filename + ' loaded (' + data.tables.length + ' tables)';
|
| 1455 |
+
document.getElementById('upload-status').style.color = 'var(--success)';
|
| 1456 |
+
document.getElementById('back-to-tasks-btn').style.display = 'inline-flex';
|
| 1457 |
+
|
| 1458 |
+
// Update schema panel
|
| 1459 |
+
renderSchema(data.tables);
|
| 1460 |
+
|
| 1461 |
+
// Clear and set placeholder
|
| 1462 |
+
document.getElementById('query-sql').value = '';
|
| 1463 |
+
document.getElementById('query-sql').placeholder = 'SELECT * FROM ' + firstCustomTable + ' LIMIT 10';
|
| 1464 |
+
|
| 1465 |
+
// Show custom mode UI
|
| 1466 |
+
updateUIForMode();
|
| 1467 |
+
|
| 1468 |
+
toast('Database uploaded successfully', 'success');
|
| 1469 |
+
} else {
|
| 1470 |
+
document.getElementById('upload-error').style.display = 'block';
|
| 1471 |
+
document.getElementById('upload-error').innerHTML = escapeHtml(data.detail || 'Upload failed');
|
| 1472 |
+
toast('Upload failed', 'error');
|
| 1473 |
+
}
|
| 1474 |
+
} catch (e) {
|
| 1475 |
+
document.getElementById('upload-error').style.display = 'block';
|
| 1476 |
+
document.getElementById('upload-error').innerHTML = 'Error: ' + escapeHtml(e.message);
|
| 1477 |
+
toast('Upload error', 'error');
|
| 1478 |
+
}
|
| 1479 |
+
|
| 1480 |
+
setLoading('upload', false);
|
| 1481 |
+
}
|
| 1482 |
+
|
| 1483 |
+
function backToTasks() {
|
| 1484 |
+
mode = 'task';
|
| 1485 |
+
customDbLoaded = false;
|
| 1486 |
+
customDbSchema = [];
|
| 1487 |
+
document.getElementById('upload-status').textContent = 'No file loaded';
|
| 1488 |
+
document.getElementById('upload-status').style.color = 'var(--text-secondary)';
|
| 1489 |
+
document.getElementById('back-to-tasks-btn').style.display = 'none';
|
| 1490 |
+
document.getElementById('db-file-input').value = '';
|
| 1491 |
+
|
| 1492 |
+
// Reset UI
|
| 1493 |
+
updateUIForMode();
|
| 1494 |
+
|
| 1495 |
+
// Reset schema panel if no task selected
|
| 1496 |
+
if (!state.currentTask) {
|
| 1497 |
+
document.getElementById('schema-panel').innerHTML = '<div class="output-body empty">Select a task to view schema</div>';
|
| 1498 |
+
} else {
|
| 1499 |
+
renderSchema(state.schemaInfo);
|
| 1500 |
+
}
|
| 1501 |
+
|
| 1502 |
+
toast('Back to RL Task mode', 'success');
|
| 1503 |
+
}
|
| 1504 |
+
|
| 1505 |
+
function updateUIForMode() {
|
| 1506 |
+
const fixTabs = document.getElementById('fix-tabs');
|
| 1507 |
+
const queryPanel = document.getElementById('investigate-panel-query');
|
| 1508 |
+
const profilePanel = document.getElementById('investigate-panel-profile');
|
| 1509 |
+
const taskSelect = document.getElementById('task-select');
|
| 1510 |
+
const resetBtn = document.getElementById('reset-btn');
|
| 1511 |
+
|
| 1512 |
+
if (mode === 'custom') {
|
| 1513 |
+
// Hide fix/submit tabs
|
| 1514 |
+
if (fixTabs) fixTabs.style.display = 'none';
|
| 1515 |
+
// Hide profile tab
|
| 1516 |
+
if (profilePanel) {
|
| 1517 |
+
const profileTab = document.querySelector('.tab[onclick*="profile"]');
|
| 1518 |
+
if (profileTab) profileTab.style.display = 'none';
|
| 1519 |
+
}
|
| 1520 |
+
// Disable task selector
|
| 1521 |
+
taskSelect.disabled = true;
|
| 1522 |
+
resetBtn.disabled = true;
|
| 1523 |
+
|
| 1524 |
+
// Add custom mode indicator
|
| 1525 |
+
let indicator = document.getElementById('custom-mode-indicator');
|
| 1526 |
+
if (!indicator) {
|
| 1527 |
+
indicator = document.createElement('div');
|
| 1528 |
+
indicator.id = 'custom-mode-indicator';
|
| 1529 |
+
indicator.className = 'help-section';
|
| 1530 |
+
indicator.style.cssText = 'background: #E3F2FD; border-color: #90CAF9; margin-top: 16px;';
|
| 1531 |
+
indicator.innerHTML = '<div style="font-size: 13px; color: #1565C0;"><strong>Custom DB Mode</strong> β No scoring available. Use SELECT queries to explore your data.</div>';
|
| 1532 |
+
queryPanel.insertBefore(indicator, queryPanel.firstChild);
|
| 1533 |
+
}
|
| 1534 |
+
indicator.style.display = 'block';
|
| 1535 |
+
|
| 1536 |
+
} else {
|
| 1537 |
+
// Show all tabs
|
| 1538 |
+
if (fixTabs) fixTabs.style.display = 'flex';
|
| 1539 |
+
const profileTab = document.querySelector('.tab[onclick*="profile"]');
|
| 1540 |
+
if (profileTab) profileTab.style.display = 'block';
|
| 1541 |
+
|
| 1542 |
+
// Enable task selector
|
| 1543 |
+
taskSelect.disabled = false;
|
| 1544 |
+
resetBtn.disabled = false;
|
| 1545 |
+
|
| 1546 |
+
// Hide custom mode indicator
|
| 1547 |
+
const indicator = document.getElementById('custom-mode-indicator');
|
| 1548 |
+
if (indicator) indicator.style.display = 'none';
|
| 1549 |
+
}
|
| 1550 |
+
}
|
| 1551 |
+
|
| 1552 |
// βββ SCHEMA RENDERING βββββββββββββββββββββββββββββββββββββββββββββββββ
|
| 1553 |
function parseColumn(col) {
|
| 1554 |
// col is either a string "email (TEXT)" or an object {name, type, nullable}
|
|
|
|
| 1601 |
html += '<tr><td class="col-name">' + escapeHtml(parsed.name) + '</td>';
|
| 1602 |
html += '<td class="col-type"><span class="' + typeClass + '">' + escapeHtml(parsed.type) + '</span></td></tr>';
|
| 1603 |
}
|
| 1604 |
+
html += '</tbody></table>';
|
| 1605 |
+
html += '<div style="margin-top: 8px; padding: 6px 8px; background: var(--surface); border-radius: 4px; font-size: 11px; color: var(--text-tertiary);">';
|
| 1606 |
+
html += 'Tip: Use <code style="background: var(--bg); padding: 1px 4px; border-radius: 3px;">SELECT * FROM ' + escapeHtml(tableName) + ' LIMIT 10</code> to explore this table';
|
| 1607 |
+
html += '</div>';
|
| 1608 |
+
html += '</div>';
|
| 1609 |
}
|
| 1610 |
panel.innerHTML = html;
|
| 1611 |
}
|