Spaces:
Running on Zero
Running on Zero
Update app.js
Browse files
app.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
| 1 |
// CV Curve Fitting Pro - Pure Python JAX + SciPy Engine Client
|
| 2 |
-
// Connected to Python
|
| 3 |
|
| 4 |
// Global State
|
| 5 |
let activeSocket = null;
|
|
@@ -33,36 +33,23 @@ function resolveEndpoints(rawUrl) {
|
|
| 33 |
}
|
| 34 |
|
| 35 |
let httpHealth = "";
|
| 36 |
-
let wsSolve = "";
|
| 37 |
let httpSolve = "";
|
| 38 |
|
| 39 |
if (url.startsWith("https://")) {
|
| 40 |
httpHealth = url + "/health";
|
| 41 |
httpSolve = url + "/api/solve";
|
| 42 |
-
wsSolve = url.replace("https://", "wss://") + "/ws/solve";
|
| 43 |
} else if (url.startsWith("http://")) {
|
| 44 |
httpHealth = url + "/health";
|
| 45 |
httpSolve = url + "/api/solve";
|
| 46 |
-
wsSolve = url.replace("http://", "ws://") + "/ws/solve";
|
| 47 |
-
} else if (url.startsWith("wss://")) {
|
| 48 |
-
wsSolve = url.includes("/ws/solve") ? url : url + "/ws/solve";
|
| 49 |
-
httpHealth = url.replace("wss://", "https://").replace(/\/ws\/solve\/?$/, "") + "/health";
|
| 50 |
-
httpSolve = url.replace("wss://", "https://").replace(/\/ws\/solve\/?$/, "") + "/api/solve";
|
| 51 |
-
} else if (url.startsWith("ws://")) {
|
| 52 |
-
wsSolve = url.includes("/ws/solve") ? url : url + "/ws/solve";
|
| 53 |
-
httpHealth = url.replace("ws://", "http://").replace(/\/ws\/solve\/?$/, "") + "/health";
|
| 54 |
-
httpSolve = url.replace("ws://", "http://").replace(/\/ws\/solve\/?$/, "") + "/api/solve";
|
| 55 |
} else {
|
| 56 |
httpHealth = "https://" + url + "/health";
|
| 57 |
httpSolve = "https://" + url + "/api/solve";
|
| 58 |
-
wsSolve = "wss://" + url + "/ws/solve";
|
| 59 |
}
|
| 60 |
|
| 61 |
return {
|
| 62 |
rawUrl: url,
|
| 63 |
httpHealth: httpHealth,
|
| 64 |
-
httpSolve: httpSolve
|
| 65 |
-
wsSolve: wsSolve
|
| 66 |
};
|
| 67 |
}
|
| 68 |
|
|
@@ -110,9 +97,9 @@ async function probePythonBackend() {
|
|
| 110 |
isBackendAvailable = true;
|
| 111 |
activeBackendType = "cloud";
|
| 112 |
if (dot) dot.className = 'status-dot online';
|
| 113 |
-
if (label) label.innerText = 'ZeroGPU
|
| 114 |
if (msg) {
|
| 115 |
-
msg.innerHTML = `<span style="color: #10b981; font-weight: 500;">✓ ZeroGPU JAX Server Active</span>`;
|
| 116 |
}
|
| 117 |
return false;
|
| 118 |
}
|
|
@@ -444,123 +431,84 @@ window.handleFormSubmit = async function(e) {
|
|
| 444 |
return false;
|
| 445 |
};
|
| 446 |
|
| 447 |
-
// Execution via
|
| 448 |
async function executePythonSolver(fileContent, config) {
|
| 449 |
const stageEl = document.getElementById('status-stage');
|
| 450 |
const detailsEl = document.getElementById('status-details');
|
| 451 |
-
if (stageEl) stageEl.innerText = '⚡ ZeroGPU
|
| 452 |
-
if (detailsEl) detailsEl.innerText = '
|
| 453 |
|
| 454 |
-
|
|
|
|
| 455 |
window.location.origin + "/api/solve",
|
| 456 |
window.location.origin + "/solve",
|
| 457 |
DEFAULT_HF_SPACE_URL + "/api/solve",
|
| 458 |
-
DEFAULT_LOCAL_URL + "/api/solve"
|
| 459 |
-
currentEndpoints.httpSolve
|
| 460 |
];
|
| 461 |
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
for (const endpoint of solveCandidates) {
|
| 465 |
try {
|
| 466 |
-
const
|
|
|
|
|
|
|
| 467 |
method: "POST",
|
| 468 |
headers: { "Content-Type": "application/json" },
|
| 469 |
body: JSON.stringify({
|
| 470 |
-
|
| 471 |
-
config: config
|
| 472 |
-
|
| 473 |
-
|
| 474 |
});
|
|
|
|
| 475 |
|
| 476 |
-
if (
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
let buffer = "";
|
| 481 |
-
streamSucceeded = true;
|
| 482 |
-
|
| 483 |
-
while (true) {
|
| 484 |
-
const { done, value } = await reader.read();
|
| 485 |
-
if (done) break;
|
| 486 |
-
|
| 487 |
-
buffer += decoder.decode(value, { stream: true });
|
| 488 |
-
const lines = buffer.split("\n");
|
| 489 |
-
buffer = lines.pop();
|
| 490 |
-
|
| 491 |
-
for (const line of lines) {
|
| 492 |
-
if (!line.trim()) continue;
|
| 493 |
-
try {
|
| 494 |
-
const data = JSON.parse(line);
|
| 495 |
-
handleSolverMessage(data);
|
| 496 |
-
} catch (err) {
|
| 497 |
-
console.error("JSON parse error in chunk:", err);
|
| 498 |
-
}
|
| 499 |
-
}
|
| 500 |
-
}
|
| 501 |
-
|
| 502 |
-
if (buffer.trim()) {
|
| 503 |
-
try {
|
| 504 |
-
const data = JSON.parse(buffer);
|
| 505 |
-
handleSolverMessage(data);
|
| 506 |
-
} catch (err) {}
|
| 507 |
}
|
| 508 |
-
|
| 509 |
-
break;
|
| 510 |
} catch (err) {
|
| 511 |
-
console.warn(`
|
| 512 |
}
|
| 513 |
}
|
| 514 |
|
| 515 |
-
|
| 516 |
-
|
| 517 |
-
|
| 518 |
-
|
|
|
|
| 519 |
|
| 520 |
-
|
| 521 |
-
const stageEl = document.getElementById('status-stage');
|
| 522 |
-
const detailsEl = document.getElementById('status-details');
|
| 523 |
-
if (stageEl) stageEl.innerText = 'Connecting via WebSocket...';
|
| 524 |
-
if (detailsEl) detailsEl.innerText = `Opening WebSocket on ${currentEndpoints.wsSolve}...`;
|
| 525 |
-
|
| 526 |
-
if (activeSocket) {
|
| 527 |
-
activeSocket.close();
|
| 528 |
-
}
|
| 529 |
-
|
| 530 |
-
try {
|
| 531 |
-
activeSocket = new WebSocket(currentEndpoints.wsSolve);
|
| 532 |
-
} catch (err) {
|
| 533 |
-
handleBackendOffline();
|
| 534 |
-
return;
|
| 535 |
-
}
|
| 536 |
-
|
| 537 |
-
activeSocket.onopen = () => {
|
| 538 |
-
if (stageEl) stageEl.innerText = '⚡ JAX Optimization Running';
|
| 539 |
-
if (detailsEl) detailsEl.innerText = 'Hardware-accelerated JAX XLA optimization in progress...';
|
| 540 |
-
|
| 541 |
-
activeSocket.send(JSON.stringify({
|
| 542 |
-
action: 'solve',
|
| 543 |
-
config: config,
|
| 544 |
-
file_content: fileContent
|
| 545 |
-
}));
|
| 546 |
-
};
|
| 547 |
-
|
| 548 |
-
activeSocket.onmessage = (event) => {
|
| 549 |
try {
|
| 550 |
-
|
| 551 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 552 |
} catch (e) {
|
| 553 |
-
console.
|
| 554 |
}
|
| 555 |
-
}
|
| 556 |
-
|
| 557 |
-
activeSocket.onerror = () => {
|
| 558 |
-
handleBackendOffline();
|
| 559 |
-
};
|
| 560 |
|
| 561 |
-
|
| 562 |
-
stopOptimizationUI();
|
| 563 |
-
};
|
| 564 |
}
|
| 565 |
|
| 566 |
function handleSolverMessage(data) {
|
|
@@ -571,7 +519,7 @@ function handleSolverMessage(data) {
|
|
| 571 |
const stageName = data.stage_name || `Stage ${data.stage}`;
|
| 572 |
const iterText = data.iteration ? ` (Iter ${data.iteration})` : '';
|
| 573 |
if (stageEl) stageEl.innerText = `⚡ ${stageName}${iterText}`;
|
| 574 |
-
if (detailsEl) detailsEl.innerText = `Objective Loss: ${data.loss.toExponential(4)} | Diffusivity D0: ${(data.d0 || 0).toExponential(3)} cm²/s`;
|
| 575 |
|
| 576 |
if (data.current_fit && window.Plotly) {
|
| 577 |
updateLivePlotProgress(data.current_fit);
|
|
@@ -583,20 +531,26 @@ function handleSolverMessage(data) {
|
|
| 583 |
stopOptimizationUI();
|
| 584 |
latestResults = data;
|
| 585 |
displayExtractedResults(data);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 586 |
} else if (data.type === 'error') {
|
| 587 |
-
|
| 588 |
-
if (detailsEl) detailsEl.innerText = data.message || 'An error occurred during calculation.';
|
| 589 |
-
stopOptimizationUI();
|
| 590 |
-
alert(`Solver Message: ${data.message}`);
|
| 591 |
}
|
| 592 |
}
|
| 593 |
|
| 594 |
-
function
|
| 595 |
const stageEl = document.getElementById('status-stage');
|
| 596 |
const detailsEl = document.getElementById('status-details');
|
| 597 |
-
if (stageEl) stageEl.innerText = '
|
| 598 |
-
if (detailsEl) detailsEl.innerText =
|
| 599 |
stopOptimizationUI();
|
|
|
|
| 600 |
}
|
| 601 |
|
| 602 |
function startOptimizationUI() {
|
|
|
|
| 1 |
// CV Curve Fitting Pro - Pure Python JAX + SciPy Engine Client
|
| 2 |
+
// Connected to Python ZeroGPU Engine (Free ZeroGPU & CPU Ready)
|
| 3 |
|
| 4 |
// Global State
|
| 5 |
let activeSocket = null;
|
|
|
|
| 33 |
}
|
| 34 |
|
| 35 |
let httpHealth = "";
|
|
|
|
| 36 |
let httpSolve = "";
|
| 37 |
|
| 38 |
if (url.startsWith("https://")) {
|
| 39 |
httpHealth = url + "/health";
|
| 40 |
httpSolve = url + "/api/solve";
|
|
|
|
| 41 |
} else if (url.startsWith("http://")) {
|
| 42 |
httpHealth = url + "/health";
|
| 43 |
httpSolve = url + "/api/solve";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 44 |
} else {
|
| 45 |
httpHealth = "https://" + url + "/health";
|
| 46 |
httpSolve = "https://" + url + "/api/solve";
|
|
|
|
| 47 |
}
|
| 48 |
|
| 49 |
return {
|
| 50 |
rawUrl: url,
|
| 51 |
httpHealth: httpHealth,
|
| 52 |
+
httpSolve: httpSolve
|
|
|
|
| 53 |
};
|
| 54 |
}
|
| 55 |
|
|
|
|
| 97 |
isBackendAvailable = true;
|
| 98 |
activeBackendType = "cloud";
|
| 99 |
if (dot) dot.className = 'status-dot online';
|
| 100 |
+
if (label) label.innerText = 'ZeroGPU A100 Engine Ready';
|
| 101 |
if (msg) {
|
| 102 |
+
msg.innerHTML = `<span style="color: #10b981; font-weight: 500;">✓ ZeroGPU JAX Server Active (100% Free)</span>`;
|
| 103 |
}
|
| 104 |
return false;
|
| 105 |
}
|
|
|
|
| 431 |
return false;
|
| 432 |
};
|
| 433 |
|
| 434 |
+
// Execution via Native ZeroGPU Pipeline & Direct HTTP API
|
| 435 |
async function executePythonSolver(fileContent, config) {
|
| 436 |
const stageEl = document.getElementById('status-stage');
|
| 437 |
const detailsEl = document.getElementById('status-details');
|
| 438 |
+
if (stageEl) stageEl.innerText = '⚡ ZeroGPU A100 Optimizing...';
|
| 439 |
+
if (detailsEl) detailsEl.innerText = 'JAX Auto-Diff multi-stage L-BFGS-B optimization in progress...';
|
| 440 |
|
| 441 |
+
// 1. Direct HTTP API Call to the Space backend
|
| 442 |
+
const endpoints = [
|
| 443 |
window.location.origin + "/api/solve",
|
| 444 |
window.location.origin + "/solve",
|
| 445 |
DEFAULT_HF_SPACE_URL + "/api/solve",
|
| 446 |
+
DEFAULT_LOCAL_URL + "/api/solve"
|
|
|
|
| 447 |
];
|
| 448 |
|
| 449 |
+
for (const endpoint of endpoints) {
|
|
|
|
|
|
|
| 450 |
try {
|
| 451 |
+
const controller = new AbortController();
|
| 452 |
+
const timeoutId = setTimeout(() => controller.abort(), 180000);
|
| 453 |
+
const res = await fetch(endpoint, {
|
| 454 |
method: "POST",
|
| 455 |
headers: { "Content-Type": "application/json" },
|
| 456 |
body: JSON.stringify({
|
| 457 |
+
file_content: fileContent,
|
| 458 |
+
config: config
|
| 459 |
+
}),
|
| 460 |
+
signal: controller.signal
|
| 461 |
});
|
| 462 |
+
clearTimeout(timeoutId);
|
| 463 |
|
| 464 |
+
if (res.ok) {
|
| 465 |
+
const data = await res.json();
|
| 466 |
+
handleSolverMessage(data);
|
| 467 |
+
return;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 468 |
}
|
|
|
|
|
|
|
| 469 |
} catch (err) {
|
| 470 |
+
console.warn(`HTTP solve attempt on ${endpoint} failed:`, err);
|
| 471 |
}
|
| 472 |
}
|
| 473 |
|
| 474 |
+
// 2. Native Gradio Event Trigger fallback
|
| 475 |
+
const grInputFile = document.querySelector('#gr_input_file textarea, #gr_input_file input');
|
| 476 |
+
const grInputConfig = document.querySelector('#gr_input_config textarea, #gr_input_config input');
|
| 477 |
+
const grBtn = document.querySelector('#gr_trigger_btn');
|
| 478 |
+
const grOutput = document.querySelector('#gr_output_json textarea, #gr_output_json input');
|
| 479 |
|
| 480 |
+
if (grInputFile && grInputConfig && grBtn) {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 481 |
try {
|
| 482 |
+
grInputFile.value = fileContent;
|
| 483 |
+
grInputFile.dispatchEvent(new Event('input', { bubbles: true }));
|
| 484 |
+
grInputConfig.value = JSON.stringify(config);
|
| 485 |
+
grInputConfig.dispatchEvent(new Event('input', { bubbles: true }));
|
| 486 |
+
|
| 487 |
+
let checkCount = 0;
|
| 488 |
+
const checkInterval = setInterval(() => {
|
| 489 |
+
checkCount++;
|
| 490 |
+
if (grOutput && grOutput.value && grOutput.value.trim().length > 0) {
|
| 491 |
+
clearInterval(checkInterval);
|
| 492 |
+
try {
|
| 493 |
+
const data = JSON.parse(grOutput.value);
|
| 494 |
+
handleSolverMessage(data);
|
| 495 |
+
} catch (e) {
|
| 496 |
+
console.error(e);
|
| 497 |
+
}
|
| 498 |
+
} else if (checkCount > 120) {
|
| 499 |
+
clearInterval(checkInterval);
|
| 500 |
+
handleSolverError("Optimization timed out.");
|
| 501 |
+
}
|
| 502 |
+
}, 1000);
|
| 503 |
+
|
| 504 |
+
grBtn.click();
|
| 505 |
+
return;
|
| 506 |
} catch (e) {
|
| 507 |
+
console.warn("Gradio trigger error:", e);
|
| 508 |
}
|
| 509 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
| 510 |
|
| 511 |
+
handleSolverError("Could not reach ZeroGPU backend server. Please check your Space status.");
|
|
|
|
|
|
|
| 512 |
}
|
| 513 |
|
| 514 |
function handleSolverMessage(data) {
|
|
|
|
| 519 |
const stageName = data.stage_name || `Stage ${data.stage}`;
|
| 520 |
const iterText = data.iteration ? ` (Iter ${data.iteration})` : '';
|
| 521 |
if (stageEl) stageEl.innerText = `⚡ ${stageName}${iterText}`;
|
| 522 |
+
if (detailsEl) detailsEl.innerText = `Objective Loss: ${data.loss ? data.loss.toExponential(4) : ''} | Diffusivity D0: ${(data.d0 || 0).toExponential(3)} cm²/s`;
|
| 523 |
|
| 524 |
if (data.current_fit && window.Plotly) {
|
| 525 |
updateLivePlotProgress(data.current_fit);
|
|
|
|
| 531 |
stopOptimizationUI();
|
| 532 |
latestResults = data;
|
| 533 |
displayExtractedResults(data);
|
| 534 |
+
|
| 535 |
+
// Update main plot with final simulation overlay
|
| 536 |
+
if (data.plots && data.plots.sim_current && window.Plotly) {
|
| 537 |
+
updateLivePlotProgress({
|
| 538 |
+
potential: data.plots.exp_potential,
|
| 539 |
+
current: data.plots.sim_current
|
| 540 |
+
});
|
| 541 |
+
}
|
| 542 |
} else if (data.type === 'error') {
|
| 543 |
+
handleSolverError(data.message || 'An error occurred during calculation.');
|
|
|
|
|
|
|
|
|
|
| 544 |
}
|
| 545 |
}
|
| 546 |
|
| 547 |
+
function handleSolverError(msg) {
|
| 548 |
const stageEl = document.getElementById('status-stage');
|
| 549 |
const detailsEl = document.getElementById('status-details');
|
| 550 |
+
if (stageEl) stageEl.innerText = '❌ Optimization Notice';
|
| 551 |
+
if (detailsEl) detailsEl.innerText = msg;
|
| 552 |
stopOptimizationUI();
|
| 553 |
+
alert(`Solver: ${msg}`);
|
| 554 |
}
|
| 555 |
|
| 556 |
function startOptimizationUI() {
|