AI Agent commited on
Commit ·
87e6e59
1
Parent(s): 03cfa1a
Fix UI 404 by applying async job queue client logic to actual index.html template
Browse files- templates/index.html +86 -50
templates/index.html
CHANGED
|
@@ -433,72 +433,108 @@
|
|
| 433 |
cpu_threads: 16
|
| 434 |
};
|
| 435 |
|
| 436 |
-
const
|
| 437 |
method: 'POST',
|
| 438 |
headers: { 'Content-Type': 'application/json', 'X-Session-Token': SESSION_TOKEN },
|
| 439 |
body: JSON.stringify(payload)
|
| 440 |
});
|
| 441 |
|
| 442 |
-
if (!
|
| 443 |
-
throw new Error(`Network response was not ok: ${
|
| 444 |
}
|
|
|
|
|
|
|
|
|
|
| 445 |
|
| 446 |
diag.info('Generation started. Streaming chunks...');
|
| 447 |
-
|
| 448 |
-
|
| 449 |
-
let
|
| 450 |
let answer = '';
|
| 451 |
|
| 452 |
-
while (!
|
| 453 |
-
|
| 454 |
-
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 477 |
}
|
| 478 |
-
} else if (data.metrics) {
|
| 479 |
-
diag.info(`Generation metrics:`, data.metrics);
|
| 480 |
-
const metricsHtml = `
|
| 481 |
-
<div style="font-size: 0.85rem; color: #666; margin-top: 15px; border-top: 1px dashed var(--border-color); padding-top: 10px;">
|
| 482 |
-
Analysis took ${data.metrics.time_seconds?.toFixed(1) || '?'} seconds.
|
| 483 |
-
Tokens: ${data.metrics.tokens_in || '?'} in, ${data.metrics.tokens_out || '?'} out.
|
| 484 |
-
</div>
|
| 485 |
-
`;
|
| 486 |
-
answerContent.innerHTML += metricsHtml;
|
| 487 |
-
window.scrollTo(0, document.body.scrollHeight);
|
| 488 |
-
} else if (data.done) {
|
| 489 |
-
diag.info('Generation complete.');
|
| 490 |
-
} else if (data.error) {
|
| 491 |
-
genGraph.style.display = 'none';
|
| 492 |
-
diag.error(`Backend error: ${data.error}`);
|
| 493 |
-
answerContent.innerHTML += `<p style="color:#d32f2f; font-weight:bold;">Error: ${data.error}</p>`;
|
| 494 |
}
|
| 495 |
-
} catch (e) {
|
| 496 |
-
// Ignore parse errors on incomplete chunks
|
| 497 |
}
|
| 498 |
}
|
| 499 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 500 |
}
|
| 501 |
-
done = readerDone;
|
| 502 |
}
|
| 503 |
} catch (e) {
|
| 504 |
genGraph.style.display = 'none';
|
|
|
|
| 433 |
cpu_threads: 16
|
| 434 |
};
|
| 435 |
|
| 436 |
+
const startRes = await fetch('/api/query/start', {
|
| 437 |
method: 'POST',
|
| 438 |
headers: { 'Content-Type': 'application/json', 'X-Session-Token': SESSION_TOKEN },
|
| 439 |
body: JSON.stringify(payload)
|
| 440 |
});
|
| 441 |
|
| 442 |
+
if (!startRes.ok) {
|
| 443 |
+
throw new Error(`Network response was not ok: ${startRes.status}`);
|
| 444 |
}
|
| 445 |
+
|
| 446 |
+
const startData = await startRes.json();
|
| 447 |
+
const jobId = startData.job_id;
|
| 448 |
|
| 449 |
diag.info('Generation started. Streaming chunks...');
|
| 450 |
+
|
| 451 |
+
let jobDone = false;
|
| 452 |
+
let currentOffset = 0;
|
| 453 |
let answer = '';
|
| 454 |
|
| 455 |
+
while (!jobDone) {
|
| 456 |
+
try {
|
| 457 |
+
const res = await fetch(`/api/query/stream/${jobId}?offset=${currentOffset}`, {
|
| 458 |
+
headers: { 'X-Session-Token': SESSION_TOKEN }
|
| 459 |
+
});
|
| 460 |
+
|
| 461 |
+
if (!res.ok) {
|
| 462 |
+
if (res.status === 404) throw new Error("Job not found or expired on server.");
|
| 463 |
+
throw new Error(`Stream network response was not ok: ${res.status}`);
|
| 464 |
+
}
|
| 465 |
+
|
| 466 |
+
const reader = res.body.getReader();
|
| 467 |
+
const decoder = new TextDecoder('utf-8');
|
| 468 |
+
let buffer = '';
|
| 469 |
+
|
| 470 |
+
while (!jobDone) {
|
| 471 |
+
const { value, done: readerDone } = await reader.read();
|
| 472 |
+
if (readerDone) break;
|
| 473 |
+
if (value) {
|
| 474 |
+
buffer += decoder.decode(value, { stream: true });
|
| 475 |
+
const lines = buffer.split('\n\n');
|
| 476 |
+
buffer = lines.pop(); // keep the last incomplete chunk in buffer
|
| 477 |
+
|
| 478 |
+
for (let line of lines) {
|
| 479 |
+
if (line.startsWith('data: ')) {
|
| 480 |
+
try {
|
| 481 |
+
const data = JSON.parse(line.substring(6));
|
| 482 |
+
|
| 483 |
+
// Successfully received an event
|
| 484 |
+
currentOffset++;
|
| 485 |
+
|
| 486 |
+
if (data.chunk) {
|
| 487 |
+
genGraph.style.display = 'none';
|
| 488 |
+
answer += data.chunk;
|
| 489 |
+
diag.info(`Received chunk: ${data.chunk}`);
|
| 490 |
+
if (window.marked) {
|
| 491 |
+
answerContent.innerHTML = marked.parse(answer);
|
| 492 |
+
} else {
|
| 493 |
+
answerContent.textContent = answer;
|
| 494 |
+
}
|
| 495 |
+
// Auto-scroll to bottom
|
| 496 |
+
window.scrollTo(0, document.body.scrollHeight);
|
| 497 |
+
} else if (data.status) {
|
| 498 |
+
diag.info(`Status update:`, data.status);
|
| 499 |
+
if (milestoneText) {
|
| 500 |
+
const msg = typeof data.status === 'string' ? data.status : (data.status.message || 'Processing...');
|
| 501 |
+
milestoneText.setAttribute('data-status', msg);
|
| 502 |
+
}
|
| 503 |
+
} else if (data.metrics) {
|
| 504 |
+
diag.info(`Generation metrics:`, data.metrics);
|
| 505 |
+
const metricsHtml = `
|
| 506 |
+
<div style="font-size: 0.85rem; color: #666; margin-top: 15px; border-top: 1px dashed var(--border-color); padding-top: 10px;">
|
| 507 |
+
Analysis took ${data.metrics.time_seconds?.toFixed(1) || '?'} seconds.
|
| 508 |
+
Tokens: ${data.metrics.tokens_in || '?'} in, ${data.metrics.tokens_out || '?'} out.
|
| 509 |
+
</div>
|
| 510 |
+
`;
|
| 511 |
+
answerContent.innerHTML += metricsHtml;
|
| 512 |
+
window.scrollTo(0, document.body.scrollHeight);
|
| 513 |
+
} else if (data.done) {
|
| 514 |
+
diag.info('Generation complete.');
|
| 515 |
+
jobDone = true;
|
| 516 |
+
break;
|
| 517 |
+
} else if (data.error) {
|
| 518 |
+
genGraph.style.display = 'none';
|
| 519 |
+
diag.error(`Backend error: ${data.error}`);
|
| 520 |
+
answerContent.innerHTML += `<p style="color:#d32f2f; font-weight:bold;">Error: ${data.error}</p>`;
|
| 521 |
+
jobDone = true;
|
| 522 |
+
break;
|
| 523 |
+
}
|
| 524 |
+
} catch (e) {
|
| 525 |
+
// Ignore parse errors on incomplete chunks
|
| 526 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 527 |
}
|
|
|
|
|
|
|
| 528 |
}
|
| 529 |
}
|
| 530 |
}
|
| 531 |
+
} catch (err) {
|
| 532 |
+
if (err.message.includes("Job not found") || jobDone) {
|
| 533 |
+
throw err;
|
| 534 |
+
}
|
| 535 |
+
diag.warn('Network dropped, reconnecting to job...', err);
|
| 536 |
+
await new Promise(r => setTimeout(r, 1500));
|
| 537 |
}
|
|
|
|
| 538 |
}
|
| 539 |
} catch (e) {
|
| 540 |
genGraph.style.display = 'none';
|