| <!DOCTYPE html> |
| <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 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 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> |
| |
| 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'); |
| |
| |
| |
| const isLocal = window.location.hostname === 'localhost' || window.location.hostname === '127.0.0.1'; |
| const apiBaseUrl = isLocal ? 'http://localhost:7860' : ''; |
| |
| |
| 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(); |
| } |
| |
| |
| async function populateModels() { |
| try { |
| const models = await apiFetch('/api/available-models', { method: 'GET' }); |
| modelSelect.innerHTML = ''; |
| 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; |
| } |
| }); |
| |
| |
| 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); |
| } |
| }); |
| |
| |
| document.addEventListener('DOMContentLoaded', populateModels); |
| </script> |
| </body> |
| </html> |