Spaces:
Sleeping
Sleeping
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>AURELIUS | BATTERY OPTIMIZER</title> | |
| <link rel="icon" type="image/png" href="/static/favicon.png"> | |
| <style> | |
| /* "Cyberpunk Lab" Aesthetic */ | |
| body { font-family: 'Courier New', monospace; background-color: #0d1117; color: #c9d1d9; max-width: 950px; margin: 40px auto; padding: 20px; font-size: 16px; } | |
| h1 { border-bottom: 1px solid #30363d; padding-bottom: 10px; color: #58a6ff; letter-spacing: 2px;} | |
| .control-panel { background: #161b22; padding: 25px; border: 1px solid #30363d; border-radius: 6px; margin-bottom: 25px; box-shadow: 0 4px 12px rgba(0,0,0,0.5); } | |
| label { display: block; margin-top: 15px; margin-bottom: 5px; color: #8b949e; font-size: 15px; text-transform: uppercase; letter-spacing: 1px; } | |
| input, textarea, button { width: 100%; box-sizing: border-box; background: #0d1117; border: 1px solid #30363d; color: #c9d1d9; font-family: inherit; font-size: 16px; } | |
| input { padding: 10px; } | |
| textarea { padding: 10px; resize: vertical; font-size: 16px; } | |
| textarea:focus { outline: none; border-color: #58a6ff; } | |
| /* Action Buttons */ | |
| .btn-group { display: flex; gap: 10px; margin-bottom: 10px; } | |
| .btn-small { width: auto; padding: 5px 15px; font-size: 14px; background: #21262d; cursor: pointer; border-radius: 4px; transition: all 0.2s; } | |
| .btn-small:hover { background: #30363d; border-color: #8b949e; } | |
| .btn-primary { margin-top: 20px; padding: 15px; background: #238636; color: white; cursor: pointer; font-weight: bold; border: none; font-size: 14px; text-transform: uppercase; letter-spacing: 1px; } | |
| .btn-primary:hover { background: #2ea043; } | |
| /* Results Table */ | |
| table { width: 100%; border-collapse: collapse; margin-top: 30px; font-size: 16px; } | |
| th, td { text-align: left; padding: 12px; border-bottom: 1px solid #21262d; } | |
| th { color: #8b949e; text-transform: uppercase; font-size: 14px; } | |
| /* Status Indicators */ | |
| .status-stable { color: #3fb950; font-weight: bold; } | |
| .status-critical { color: #f85149; font-weight: bold; } | |
| .status-warn { color: #d29922; } | |
| /* Animation */ | |
| .log-entry { opacity: 0; animation: fadeIn 0.4s forwards; } | |
| @keyframes fadeIn { from { opacity: 0; transform: translateY(-10px); } to { opacity: 1; transform: translateY(0); } } | |
| </style> | |
| </head> | |
| <body> | |
| <h1>AURELIUS // BATTERY OPTIMIZER</h1> | |
| <div class="control-panel"> | |
| <div style="display: flex; gap: 20px;"> | |
| <div style="flex: 1;"> | |
| <label>Host Formula</label> | |
| <input type="text" id="host" value="Li3PS4"> | |
| </div> | |
| <div style="flex: 1;"> | |
| <label>Substitution Site</label> | |
| <input type="text" id="site" value="S"> | |
| </div> | |
| </div> | |
| <label>Doping Strategies (JSON Batch)</label> | |
| <div class="btn-group"> | |
| <button class="btn-small" onclick="loadExample('simple')">LOAD: Single Dopants</button> | |
| <button class="btn-small" onclick="loadExample('complex')">LOAD: High-Entropy Mix</button> | |
| <button class="btn-small" onclick="loadExample('stress')">LOAD: Stress Test (Solubility)</button> | |
| </div> | |
| <textarea id="jsonInput" rows="8"></textarea> | |
| <button class="btn-primary" onclick="startSimulation()">>> INITIATE SIMULATION STREAM</button> | |
| </div> | |
| <div id="statusLine" style="color: #8b949e; margin-bottom: 10px; font-size: 15px; min-height: 20px;">SYSTEM READY. AWAITING INPUT.</div> | |
| <table id="resultsTable"> | |
| <thead> | |
| <tr> | |
| <th style="width: 30%">Composition Strategy</th> | |
| <th>Pred. Voltage</th> | |
| <th>Lattice Strain</th> | |
| <th>Stability Verdict</th> | |
| </tr> | |
| </thead> | |
| <tbody></tbody> | |
| </table> | |
| <script> | |
| // --- 1. PRE-LOAD DATA FUNCTION --- | |
| function loadExample(type) { | |
| const data = { | |
| simple: `[ | |
| {"Cl": 0.1}, | |
| {"Br": 0.1}, | |
| {"I": 0.05} | |
| ]`, | |
| complex: `[ | |
| {"Cl": 0.1, "Br": 0.1}, | |
| {"I": 0.05, "F": 0.05}, | |
| {"Cl": 0.2, "I": 0.02}, | |
| {"Br": 0.15, "Cl": 0.05} | |
| ]`, | |
| stress: `[ | |
| {"Cl": 0.9}, | |
| {"I": 0.5}, | |
| {"F": 0.5, "Cl": 0.5} | |
| ]` | |
| }; | |
| document.getElementById("jsonInput").value = data[type]; | |
| document.getElementById("jsonInput").style.borderColor = "#30363d"; // Reset color | |
| } | |
| // Initialize with simple data | |
| loadExample('simple'); | |
| // --- 2. MAIN SIMULATION LOGIC --- | |
| async function startSimulation() { | |
| const tableBody = document.querySelector("#resultsTable tbody"); | |
| const statusLine = document.getElementById("statusLine"); | |
| const inputArea = document.getElementById("jsonInput"); | |
| // UX: Reset | |
| tableBody.innerHTML = ""; | |
| statusLine.innerText = "PARSING BATCH INSTRUCTIONS..."; | |
| inputArea.style.borderColor = "#30363d"; | |
| // A. VALIDATE JSON | |
| let parsedRecipes; | |
| try { | |
| parsedRecipes = JSON.parse(inputArea.value); | |
| } catch (e) { | |
| statusLine.innerHTML = `<span style="color: #f85149">⚠️ SYNTAX ERROR: ${e.message}</span>`; | |
| inputArea.style.borderColor = "#f85149"; | |
| return; | |
| } | |
| if (!Array.isArray(parsedRecipes)) { | |
| statusLine.innerHTML = `<span style="color: #f85149">⚠️ FORMAT ERROR: Root must be a list [...]</span>`; | |
| return; | |
| } | |
| // B. CONNECT TO STREAM | |
| statusLine.innerText = "ESTABLISHING UPLINK TO PHYSICS ENGINE..."; | |
| try { | |
| const payload = { | |
| host_formula: document.getElementById("host").value, | |
| host_site_element: document.getElementById("site").value, | |
| recipes: parsedRecipes | |
| }; | |
| const response = await fetch("/simulate_stream", { | |
| method: "POST", | |
| headers: { "Content-Type": "application/json" }, | |
| body: JSON.stringify(payload) | |
| }); | |
| if (!response.ok) throw new Error(await response.text()); | |
| // C. READ THE STREAM | |
| const reader = response.body.getReader(); | |
| const decoder = new TextDecoder(); | |
| statusLine.innerText = "RECEIVING TELEMETRY STREAM..."; | |
| while (true) { | |
| const { done, value } = await reader.read(); | |
| if (done) break; | |
| const chunk = decoder.decode(value, { stream: true }); | |
| const lines = chunk.split("\n"); | |
| for (const line of lines) { | |
| if (!line.trim()) continue; | |
| try { | |
| const data = JSON.parse(line); | |
| if (data.type === "meta") { | |
| const source = data.base_properties.source.includes("Real") ? "REAL" : "ESTIMATED"; | |
| statusLine.innerText = `CONNECTED: ${data.host_system} | DATA SOURCE: ${source}`; | |
| } | |
| else if (data.type === "data") { | |
| addRow(data); | |
| } | |
| } catch (e) { console.error(e); } | |
| } | |
| } | |
| statusLine.innerText = "BATCH PROCESSING COMPLETE."; | |
| } catch (err) { | |
| statusLine.innerHTML = `<span style="color: #f85149">❌ SYSTEM FAILURE: ${err.message}</span>`; | |
| } | |
| } | |
| function addRow(data) { | |
| const tableBody = document.querySelector("#resultsTable tbody"); | |
| const row = document.createElement("tr"); | |
| row.className = "log-entry"; | |
| let statusClass = "status-warn"; | |
| if (data.stability_status.includes("Stable")) statusClass = "status-stable"; | |
| if (data.stability_status.includes("Critical") || data.stability_status.includes("Collapse")) statusClass = "status-critical"; | |
| row.innerHTML = ` | |
| <td><strong style="color: #c9d1d9">${data.recipe_description}</strong></td> | |
| <td>${data.predicted_voltage.toFixed(3)} V</td> | |
| <td>${data.lattice_strain.toFixed(1)} MJ</td> | |
| <td class="${statusClass}">${data.stability_status}</td> | |
| `; | |
| tableBody.appendChild(row); | |
| } | |
| </script> | |
| </body> | |
| </html> | |