|
|
<!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> |