kronos / index.html
yangyang158's picture
feat: Integrate UI control panel and fix CORS issue
8334f8b
<!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 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>