Spaces:
Running
Running
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>Kronos API Prediction Visualizer</title> | |
| <script src="https://cdn.jsdelivr.net/npm/echarts@5.3.3/dist/echarts.min.js"></script> | |
| <style> | |
| body { font-family: sans-serif; margin: 2em; } | |
| .container { max-width: 1200px; margin: auto; } | |
| .form-group { margin-bottom: 1em; } | |
| label { display: block; margin-bottom: 0.5em; } | |
| input, textarea, select { width: 100%; padding: 0.5em; box-sizing: border-box; } | |
| textarea { min-height: 150px; } | |
| button { padding: 0.7em 1.5em; cursor: pointer; } | |
| #chart { width: 100%; height: 600px; margin-top: 2em; border: 1px solid #ccc; } | |
| .error { color: red; } | |
| .success { color: green; } | |
| .status { margin-top: 1em; font-weight: bold; } | |
| .info { background-color: #f0f0f0; border-left: 4px solid #007bff; padding: 1em; margin-bottom: 1em; } | |
| hr { margin: 2em 0; } | |
| </style> | |
| </head> | |
| <body> | |
| <div class="container"> | |
| <h1>Kronos API Control Panel</h1> | |
| <div class="info"> | |
| <p> | |
| For local testing, first run <code>app.py</code> in VS Code (press F5). The API endpoints will be available at <code>http://localhost:7860</code>. | |
| </p> | |
| </div> | |
| <!-- Section for Model Loading --> | |
| <section id="model-loader"> | |
| <h2>1. Load Model</h2> | |
| <div class="form-group"> | |
| <label for="model-select">Available Models:</label> | |
| <select id="model-select"></select> | |
| </div> | |
| <button id="load-model-btn">Load Selected Model</button> | |
| <div id="model-status" class="status"></div> | |
| </section> | |
| <hr> | |
| <!-- Section for Prediction --> | |
| <section id="predictor"> | |
| <h2>2. Get Prediction</h2> | |
| <div class="form-group"> | |
| <label for="api-key">API Key (Bearer Token, if required):</label> | |
| <input type="password" id="api-key" placeholder="Enter your API Key"> | |
| </div> | |
| <div class="form-group"> | |
| <label for="k-lines">K-line Data (JSON Array of Arrays):</label> | |
| <textarea id="k-lines" placeholder="Paste your k-line data here..."></textarea> | |
| </div> | |
| <div class="form-group"> | |
| <label for="pred-len">Prediction Length:</label> | |
| <input type="number" id="pred-len" value="120"> | |
| </div> | |
| <button id="predict-btn">Get Prediction & Visualize</button> | |
| </section> | |
| <div id="chart"></div> | |
| <div id="error-message" class="status error"></div> | |
| </div> | |
| <script> | |
| // --- Global DOM Elements --- | |
| const modelSelect = document.getElementById('model-select'); | |
| const loadModelBtn = document.getElementById('load-model-btn'); | |
| const modelStatusDiv = document.getElementById('model-status'); | |
| const predictBtn = document.getElementById('predict-btn'); | |
| const apiKeyInput = document.getElementById('api-key'); | |
| const kLinesTextarea = document.getElementById('k-lines'); | |
| const predLenInput = document.getElementById('pred-len'); | |
| const chartDom = document.getElementById('chart'); | |
| const errorDiv = document.getElementById('error-message'); | |
| // --- API Base URLs --- | |
| // Use relative paths for deployed environment, detect local for testing. | |
| const isLocal = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'; | |
| const apiBaseUrl = isLocal ? 'http://localhost:7860' : ''; | |
| // --- Helper Functions --- | |
| async function apiFetch(endpoint, options) { | |
| const apiKey = apiKeyInput.value; | |
| const headers = { | |
| 'Content-Type': 'application/json', | |
| ...options.headers, | |
| }; | |
| if (apiKey) { | |
| headers['Authorization'] = `Bearer ${apiKey}`; | |
| } | |
| const response = await fetch(apiBaseUrl + endpoint, { ...options, headers }); | |
| if (!response.ok) { | |
| const errorData = await response.json(); | |
| throw new Error(`API Error (${response.status}): ${errorData.error || 'Unknown error'}`); | |
| } | |
| return response.json(); | |
| } | |
| // --- Model Loading Logic --- | |
| async function populateModels() { | |
| try { | |
| const models = await apiFetch('/api/available-models', { method: 'GET' }); | |
| modelSelect.innerHTML = ''; // Clear existing options | |
| for (const key in models) { | |
| const option = document.createElement('option'); | |
| option.value = key; | |
| option.textContent = `${models[key].name} (${models[key].params}) - ${models[key].description}`; | |
| modelSelect.appendChild(option); | |
| } | |
| } catch (error) { | |
| modelStatusDiv.className = 'status error'; | |
| modelStatusDiv.textContent = `Failed to fetch models: ${error.message}`; | |
| } | |
| } | |
| loadModelBtn.addEventListener('click', async () => { | |
| const modelKey = modelSelect.value; | |
| modelStatusDiv.className = 'status'; | |
| modelStatusDiv.textContent = `Loading model '${modelKey}'...`; | |
| try { | |
| const result = await apiFetch('/api/load-model', { | |
| method: 'POST', | |
| body: JSON.stringify({ model_key: modelKey }) | |
| }); | |
| modelStatusDiv.className = 'status success'; | |
| modelStatusDiv.textContent = result.status; | |
| } catch (error) { | |
| modelStatusDiv.className = 'status error'; | |
| modelStatusDiv.textContent = error.message; | |
| } | |
| }); | |
| // --- Prediction Logic --- | |
| predictBtn.addEventListener('click', async () => { | |
| const kLinesText = kLinesTextarea.value; | |
| const predLen = parseInt(predLenInput.value, 10); | |
| errorDiv.textContent = ''; | |
| const myChart = echarts.init(chartDom); | |
| myChart.showLoading(); | |
| if (!kLinesText) { | |
| errorDiv.textContent = 'K-line data cannot be empty.'; | |
| myChart.hideLoading(); | |
| return; | |
| } | |
| let kLines; | |
| try { | |
| kLines = JSON.parse(kLinesText); | |
| } catch (e) { | |
| errorDiv.textContent = 'Invalid JSON in K-line data. Please check the format.'; | |
| myChart.hideLoading(); | |
| return; | |
| } | |
| const payload = { | |
| k_lines: kLines, | |
| prediction_params: { pred_len: predLen } | |
| }; | |
| try { | |
| const result = await apiFetch('/api/predict', { | |
| method: 'POST', | |
| body: JSON.stringify(payload) | |
| }); | |
| const historicalData = kLines.map(item => [ | |
| item[0], parseFloat(item[1]), parseFloat(item[4]), parseFloat(item[3]), parseFloat(item[2]) | |
| ]); | |
| const predictionData = result.prediction_results.map(item => [ | |
| item[0], parseFloat(item[1]), parseFloat(item[4]), parseFloat(item[3]), parseFloat(item[2]) | |
| ]); | |
| const allTimestamps = [...historicalData.map(d => d[0]), ...predictionData.map(d => d[0])]; | |
| const option = { | |
| tooltip: { trigger: 'axis', axisPointer: { type: 'cross' } }, | |
| legend: { data: ['Historical', 'Prediction'] }, | |
| grid: { left: '10%', right: '10%', bottom: '15%' }, | |
| xAxis: { type: 'time', min: allTimestamps[0], max: allTimestamps[allTimestamps.length - 1] }, | |
| yAxis: { scale: true, splitArea: { show: true } }, | |
| dataZoom: [ | |
| { type: 'inside', start: 50, end: 100 }, | |
| { show: true, type: 'slider', top: '90%', start: 50, end: 100 } | |
| ], | |
| series: [ | |
| { name: 'Historical', type: 'candlestick', data: historicalData, itemStyle: { color: '#00da3c', color0: '#ec0000', borderColor: '#008F28', borderColor0: '#8A0000' } }, | |
| { name: 'Prediction', type: 'candlestick', data: predictionData, itemStyle: { color: '#4287f5', color0: '#f54242', borderColor: '#285199', borderColor0: '#992828' } } | |
| ] | |
| }; | |
| myChart.hideLoading(); | |
| myChart.setOption(option); | |
| } catch (error) { | |
| myChart.hideLoading(); | |
| errorDiv.textContent = error.message; | |
| console.error('Fetch error:', error); | |
| } | |
| }); | |
| // --- Initial Load --- | |
| document.addEventListener('DOMContentLoaded', populateModels); | |
| </script> | |
| </body> | |
| </html> |