anotate ui phase 1
This commit is contained in:
188
TESTCASES/web/templates/components/annotation_list.html
Normal file
188
TESTCASES/web/templates/components/annotation_list.html
Normal file
@@ -0,0 +1,188 @@
|
||||
<div class="card annotation-list mb-3">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-tags"></i>
|
||||
Annotations
|
||||
</h6>
|
||||
<button class="btn btn-sm btn-outline-light" id="export-annotations-btn" title="Export">
|
||||
<i class="fas fa-download"></i>
|
||||
</button>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<div class="list-group list-group-flush" id="annotations-list">
|
||||
<!-- Annotations will be dynamically added here -->
|
||||
<div class="text-center text-muted py-3" id="no-annotations-msg">
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<p class="mb-0 small">No annotations yet</p>
|
||||
<p class="mb-0 small">Click on charts to create</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Export annotations
|
||||
document.getElementById('export-annotations-btn').addEventListener('click', function() {
|
||||
fetch('/api/export-annotations', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
symbol: appState.currentSymbol,
|
||||
format: 'json'
|
||||
})
|
||||
})
|
||||
.then(response => response.blob())
|
||||
.then(blob => {
|
||||
const url = window.URL.createObjectURL(blob);
|
||||
const a = document.createElement('a');
|
||||
a.href = url;
|
||||
a.download = `annotations_${appState.currentSymbol}_${Date.now()}.json`;
|
||||
document.body.appendChild(a);
|
||||
a.click();
|
||||
window.URL.revokeObjectURL(url);
|
||||
a.remove();
|
||||
showSuccess('Annotations exported successfully');
|
||||
})
|
||||
.catch(error => {
|
||||
showError('Failed to export annotations: ' + error.message);
|
||||
});
|
||||
});
|
||||
|
||||
// Function to render annotations list
|
||||
function renderAnnotationsList(annotations) {
|
||||
const listContainer = document.getElementById('annotations-list');
|
||||
const noAnnotationsMsg = document.getElementById('no-annotations-msg');
|
||||
|
||||
if (annotations.length === 0) {
|
||||
noAnnotationsMsg.style.display = 'block';
|
||||
return;
|
||||
}
|
||||
|
||||
noAnnotationsMsg.style.display = 'none';
|
||||
|
||||
// Clear existing items (except the no-annotations message)
|
||||
Array.from(listContainer.children).forEach(child => {
|
||||
if (child.id !== 'no-annotations-msg') {
|
||||
child.remove();
|
||||
}
|
||||
});
|
||||
|
||||
// Add annotation items
|
||||
annotations.forEach(annotation => {
|
||||
const item = document.createElement('div');
|
||||
item.className = 'list-group-item list-group-item-action p-2';
|
||||
item.setAttribute('data-annotation-id', annotation.annotation_id);
|
||||
|
||||
const profitClass = annotation.profit_loss_pct >= 0 ? 'text-success' : 'text-danger';
|
||||
const directionIcon = annotation.direction === 'LONG' ? 'fa-arrow-up' : 'fa-arrow-down';
|
||||
|
||||
item.innerHTML = `
|
||||
<div class="d-flex justify-content-between align-items-start">
|
||||
<div class="flex-grow-1">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<i class="fas ${directionIcon} me-1"></i>
|
||||
<strong class="small">${annotation.direction}</strong>
|
||||
<span class="badge bg-secondary ms-2 small">${annotation.timeframe}</span>
|
||||
</div>
|
||||
<div class="small text-muted">
|
||||
${new Date(annotation.entry.timestamp).toLocaleString()}
|
||||
</div>
|
||||
<div class="small ${profitClass} fw-bold">
|
||||
${annotation.profit_loss_pct >= 0 ? '+' : ''}${annotation.profit_loss_pct.toFixed(2)}%
|
||||
</div>
|
||||
</div>
|
||||
<div class="btn-group-vertical btn-group-sm">
|
||||
<button class="btn btn-sm btn-outline-primary view-annotation-btn" title="View">
|
||||
<i class="fas fa-eye"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-success generate-testcase-btn" title="Generate Test Case">
|
||||
<i class="fas fa-file-code"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-danger delete-annotation-btn" title="Delete">
|
||||
<i class="fas fa-trash"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Add event listeners
|
||||
item.querySelector('.view-annotation-btn').addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
viewAnnotation(annotation);
|
||||
});
|
||||
|
||||
item.querySelector('.generate-testcase-btn').addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
generateTestCase(annotation.annotation_id);
|
||||
});
|
||||
|
||||
item.querySelector('.delete-annotation-btn').addEventListener('click', function(e) {
|
||||
e.stopPropagation();
|
||||
deleteAnnotation(annotation.annotation_id);
|
||||
});
|
||||
|
||||
listContainer.appendChild(item);
|
||||
});
|
||||
|
||||
// Update annotation count
|
||||
document.getElementById('annotation-count').textContent = annotations.length;
|
||||
}
|
||||
|
||||
function viewAnnotation(annotation) {
|
||||
// Navigate to annotation time and highlight it
|
||||
if (appState.timeNavigator) {
|
||||
appState.timeNavigator.navigateToTime(new Date(annotation.entry.timestamp).getTime());
|
||||
}
|
||||
if (appState.chartManager) {
|
||||
appState.chartManager.highlightAnnotation(annotation.annotation_id);
|
||||
}
|
||||
}
|
||||
|
||||
function generateTestCase(annotationId) {
|
||||
fetch('/api/generate-test-case', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({annotation_id: annotationId})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
showSuccess('Test case generated successfully');
|
||||
} else {
|
||||
showError('Failed to generate test case: ' + data.error.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showError('Network error: ' + error.message);
|
||||
});
|
||||
}
|
||||
|
||||
function deleteAnnotation(annotationId) {
|
||||
if (!confirm('Are you sure you want to delete this annotation?')) {
|
||||
return;
|
||||
}
|
||||
|
||||
fetch('/api/delete-annotation', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({annotation_id: annotationId})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Remove from UI
|
||||
appState.annotations = appState.annotations.filter(a => a.annotation_id !== annotationId);
|
||||
renderAnnotationsList(appState.annotations);
|
||||
if (appState.chartManager) {
|
||||
appState.chartManager.removeAnnotation(annotationId);
|
||||
}
|
||||
showSuccess('Annotation deleted');
|
||||
} else {
|
||||
showError('Failed to delete annotation: ' + data.error.message);
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showError('Network error: ' + error.message);
|
||||
});
|
||||
}
|
||||
</script>
|
||||
99
TESTCASES/web/templates/components/chart_panel.html
Normal file
99
TESTCASES/web/templates/components/chart_panel.html
Normal file
@@ -0,0 +1,99 @@
|
||||
<div class="card chart-panel">
|
||||
<div class="card-header d-flex justify-content-between align-items-center">
|
||||
<h5 class="mb-0">
|
||||
<i class="fas fa-chart-candlestick"></i>
|
||||
Multi-Timeframe Charts
|
||||
</h5>
|
||||
<div class="btn-group btn-group-sm" role="group">
|
||||
<button type="button" class="btn btn-outline-light" id="zoom-in-btn" title="Zoom In">
|
||||
<i class="fas fa-search-plus"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-light" id="zoom-out-btn" title="Zoom Out">
|
||||
<i class="fas fa-search-minus"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-light" id="reset-zoom-btn" title="Reset Zoom">
|
||||
<i class="fas fa-expand"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-outline-light" id="fullscreen-btn" title="Fullscreen">
|
||||
<i class="fas fa-expand-arrows-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<!-- Chart container with multiple timeframes -->
|
||||
<div id="chart-container">
|
||||
<!-- Timeframe charts will be dynamically created here -->
|
||||
<div class="timeframe-chart" id="chart-1s">
|
||||
<div class="chart-header">
|
||||
<span class="timeframe-label">1 Second</span>
|
||||
<span class="chart-info" id="info-1s"></span>
|
||||
</div>
|
||||
<div class="chart-plot" id="plot-1s"></div>
|
||||
</div>
|
||||
|
||||
<div class="timeframe-chart" id="chart-1m">
|
||||
<div class="chart-header">
|
||||
<span class="timeframe-label">1 Minute</span>
|
||||
<span class="chart-info" id="info-1m"></span>
|
||||
</div>
|
||||
<div class="chart-plot" id="plot-1m"></div>
|
||||
</div>
|
||||
|
||||
<div class="timeframe-chart" id="chart-1h">
|
||||
<div class="chart-header">
|
||||
<span class="timeframe-label">1 Hour</span>
|
||||
<span class="chart-info" id="info-1h"></span>
|
||||
</div>
|
||||
<div class="chart-plot" id="plot-1h"></div>
|
||||
</div>
|
||||
|
||||
<div class="timeframe-chart" id="chart-1d">
|
||||
<div class="chart-header">
|
||||
<span class="timeframe-label">1 Day</span>
|
||||
<span class="chart-info" id="info-1d"></span>
|
||||
</div>
|
||||
<div class="chart-plot" id="plot-1d"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Loading overlay -->
|
||||
<div id="chart-loading" class="chart-loading d-none">
|
||||
<div class="spinner-border text-primary" role="status">
|
||||
<span class="visually-hidden">Loading...</span>
|
||||
</div>
|
||||
<p class="mt-2">Loading chart data...</p>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Chart panel controls
|
||||
document.getElementById('zoom-in-btn').addEventListener('click', function() {
|
||||
if (appState.chartManager) {
|
||||
appState.chartManager.handleZoom(1.5);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('zoom-out-btn').addEventListener('click', function() {
|
||||
if (appState.chartManager) {
|
||||
appState.chartManager.handleZoom(0.67);
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('reset-zoom-btn').addEventListener('click', function() {
|
||||
if (appState.chartManager) {
|
||||
appState.chartManager.resetZoom();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('fullscreen-btn').addEventListener('click', function() {
|
||||
const chartContainer = document.getElementById('chart-container');
|
||||
if (chartContainer.requestFullscreen) {
|
||||
chartContainer.requestFullscreen();
|
||||
} else if (chartContainer.webkitRequestFullscreen) {
|
||||
chartContainer.webkitRequestFullscreen();
|
||||
} else if (chartContainer.msRequestFullscreen) {
|
||||
chartContainer.msRequestFullscreen();
|
||||
}
|
||||
});
|
||||
</script>
|
||||
171
TESTCASES/web/templates/components/control_panel.html
Normal file
171
TESTCASES/web/templates/components/control_panel.html
Normal file
@@ -0,0 +1,171 @@
|
||||
<div class="card control-panel mb-3">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-sliders-h"></i>
|
||||
Controls
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<!-- Symbol Selection -->
|
||||
<div class="mb-3">
|
||||
<label for="symbol-select" class="form-label">Symbol</label>
|
||||
<select class="form-select form-select-sm" id="symbol-select">
|
||||
<option value="ETH/USDT" selected>ETH/USDT</option>
|
||||
<option value="BTC/USDT">BTC/USDT</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Timeframe Selection -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Timeframes</label>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="tf-1s" value="1s" checked>
|
||||
<label class="form-check-label" for="tf-1s">1 Second</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="tf-1m" value="1m" checked>
|
||||
<label class="form-check-label" for="tf-1m">1 Minute</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="tf-1h" value="1h" checked>
|
||||
<label class="form-check-label" for="tf-1h">1 Hour</label>
|
||||
</div>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox" id="tf-1d" value="1d" checked>
|
||||
<label class="form-check-label" for="tf-1d">1 Day</label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Time Navigation -->
|
||||
<div class="mb-3">
|
||||
<label for="date-picker" class="form-label">Navigate to Date</label>
|
||||
<input type="datetime-local" class="form-control form-control-sm" id="date-picker">
|
||||
<button class="btn btn-primary btn-sm w-100 mt-2" id="goto-date-btn">
|
||||
<i class="fas fa-calendar-day"></i>
|
||||
Go to Date
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Time Range Selector -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Quick Range</label>
|
||||
<div class="btn-group-vertical w-100" role="group">
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-range="1h">1 Hour</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-range="4h">4 Hours</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-range="1d">1 Day</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-secondary" data-range="1w">1 Week</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Navigation Buttons -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Navigate</label>
|
||||
<div class="btn-group w-100" role="group">
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" id="nav-backward-btn" title="Backward">
|
||||
<i class="fas fa-chevron-left"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" id="nav-now-btn" title="Now">
|
||||
<i class="fas fa-clock"></i>
|
||||
</button>
|
||||
<button type="button" class="btn btn-sm btn-outline-primary" id="nav-forward-btn" title="Forward">
|
||||
<i class="fas fa-chevron-right"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Annotation Mode -->
|
||||
<div class="mb-3">
|
||||
<label class="form-label">Annotation Mode</label>
|
||||
<div class="form-check form-switch">
|
||||
<input class="form-check-input" type="checkbox" id="annotation-mode-toggle" checked>
|
||||
<label class="form-check-label" for="annotation-mode-toggle">
|
||||
<span id="annotation-mode-label">Enabled</span>
|
||||
</label>
|
||||
</div>
|
||||
<small class="text-muted">Click charts to mark trades</small>
|
||||
</div>
|
||||
|
||||
<!-- Current Annotation Status -->
|
||||
<div class="mb-3" id="pending-annotation-status" style="display: none;">
|
||||
<div class="alert alert-info py-2 px-2 mb-0">
|
||||
<small>
|
||||
<i class="fas fa-info-circle"></i>
|
||||
<strong>Entry marked</strong><br>
|
||||
Click to mark exit point
|
||||
</small>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Symbol selection
|
||||
document.getElementById('symbol-select').addEventListener('change', function(e) {
|
||||
appState.currentSymbol = e.target.value;
|
||||
loadInitialData();
|
||||
});
|
||||
|
||||
// Timeframe checkboxes
|
||||
document.querySelectorAll('.form-check-input[id^="tf-"]').forEach(checkbox => {
|
||||
checkbox.addEventListener('change', function() {
|
||||
const timeframes = Array.from(document.querySelectorAll('.form-check-input[id^="tf-"]:checked'))
|
||||
.map(cb => cb.value);
|
||||
appState.currentTimeframes = timeframes;
|
||||
loadInitialData();
|
||||
});
|
||||
});
|
||||
|
||||
// Date picker navigation
|
||||
document.getElementById('goto-date-btn').addEventListener('click', function() {
|
||||
const dateValue = document.getElementById('date-picker').value;
|
||||
if (dateValue && appState.timeNavigator) {
|
||||
const timestamp = new Date(dateValue).getTime();
|
||||
appState.timeNavigator.navigateToTime(timestamp);
|
||||
}
|
||||
});
|
||||
|
||||
// Quick range buttons
|
||||
document.querySelectorAll('[data-range]').forEach(button => {
|
||||
button.addEventListener('click', function() {
|
||||
const range = this.getAttribute('data-range');
|
||||
if (appState.timeNavigator) {
|
||||
appState.timeNavigator.setTimeRange(range);
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
// Navigation buttons
|
||||
document.getElementById('nav-backward-btn').addEventListener('click', function() {
|
||||
if (appState.timeNavigator) {
|
||||
appState.timeNavigator.scrollBackward();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('nav-now-btn').addEventListener('click', function() {
|
||||
if (appState.timeNavigator) {
|
||||
appState.timeNavigator.navigateToNow();
|
||||
}
|
||||
});
|
||||
|
||||
document.getElementById('nav-forward-btn').addEventListener('click', function() {
|
||||
if (appState.timeNavigator) {
|
||||
appState.timeNavigator.scrollForward();
|
||||
}
|
||||
});
|
||||
|
||||
// Annotation mode toggle
|
||||
document.getElementById('annotation-mode-toggle').addEventListener('change', function(e) {
|
||||
const label = document.getElementById('annotation-mode-label');
|
||||
if (e.target.checked) {
|
||||
label.textContent = 'Enabled';
|
||||
if (appState.annotationManager) {
|
||||
appState.annotationManager.enable();
|
||||
}
|
||||
} else {
|
||||
label.textContent = 'Disabled';
|
||||
if (appState.annotationManager) {
|
||||
appState.annotationManager.disable();
|
||||
}
|
||||
}
|
||||
});
|
||||
</script>
|
||||
253
TESTCASES/web/templates/components/inference_panel.html
Normal file
253
TESTCASES/web/templates/components/inference_panel.html
Normal file
@@ -0,0 +1,253 @@
|
||||
<div class="inference-panel">
|
||||
<!-- Inference Controls -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-md-8">
|
||||
<h6>Inference Simulation</h6>
|
||||
<p class="text-muted small mb-0">
|
||||
Replay annotated periods with model predictions to measure performance
|
||||
</p>
|
||||
</div>
|
||||
<div class="col-md-4 text-end">
|
||||
<div class="btn-group" role="group">
|
||||
<button class="btn btn-sm btn-outline-primary" id="inference-play-btn">
|
||||
<i class="fas fa-play"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary" id="inference-pause-btn" disabled>
|
||||
<i class="fas fa-pause"></i>
|
||||
</button>
|
||||
<button class="btn btn-sm btn-outline-primary" id="inference-stop-btn" disabled>
|
||||
<i class="fas fa-stop"></i>
|
||||
</button>
|
||||
</div>
|
||||
<select class="form-select form-select-sm d-inline-block w-auto ms-2" id="inference-speed-select">
|
||||
<option value="1">1x</option>
|
||||
<option value="2">2x</option>
|
||||
<option value="5">5x</option>
|
||||
<option value="10">10x</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inference Chart -->
|
||||
<div class="row mb-3">
|
||||
<div class="col-12">
|
||||
<div id="inference-chart" style="height: 400px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Performance Metrics -->
|
||||
<div class="row">
|
||||
<div class="col-md-3">
|
||||
<div class="card bg-dark">
|
||||
<div class="card-body text-center py-2">
|
||||
<div class="small text-muted">Accuracy</div>
|
||||
<div class="h4 mb-0" id="metric-accuracy">--</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card bg-dark">
|
||||
<div class="card-body text-center py-2">
|
||||
<div class="small text-muted">Precision</div>
|
||||
<div class="h4 mb-0" id="metric-precision">--</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card bg-dark">
|
||||
<div class="card-body text-center py-2">
|
||||
<div class="small text-muted">Recall</div>
|
||||
<div class="h4 mb-0" id="metric-recall">--</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<div class="card bg-dark">
|
||||
<div class="card-body text-center py-2">
|
||||
<div class="small text-muted">F1 Score</div>
|
||||
<div class="h4 mb-0" id="metric-f1">--</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Prediction Timeline -->
|
||||
<div class="row mt-3">
|
||||
<div class="col-12">
|
||||
<h6>Prediction Timeline</h6>
|
||||
<div class="table-responsive" style="max-height: 300px; overflow-y: auto;">
|
||||
<table class="table table-sm table-dark table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>Time</th>
|
||||
<th>Prediction</th>
|
||||
<th>Confidence</th>
|
||||
<th>Actual</th>
|
||||
<th>Result</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="prediction-timeline-body">
|
||||
<tr>
|
||||
<td colspan="5" class="text-center text-muted">
|
||||
No predictions yet
|
||||
</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Confusion Matrix -->
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-6">
|
||||
<h6>Confusion Matrix</h6>
|
||||
<table class="table table-sm table-dark table-bordered text-center">
|
||||
<thead>
|
||||
<tr>
|
||||
<th></th>
|
||||
<th colspan="2">Predicted</th>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>Actual</th>
|
||||
<th>BUY</th>
|
||||
<th>SELL</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<th>BUY</th>
|
||||
<td id="cm-tp-buy">0</td>
|
||||
<td id="cm-fn-buy">0</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<th>SELL</th>
|
||||
<td id="cm-fp-sell">0</td>
|
||||
<td id="cm-tn-sell">0</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<h6>Prediction Distribution</h6>
|
||||
<div id="prediction-distribution-chart" style="height: 200px;"></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
let inferenceState = {
|
||||
isPlaying: false,
|
||||
currentIndex: 0,
|
||||
predictions: [],
|
||||
annotations: [],
|
||||
speed: 1
|
||||
};
|
||||
|
||||
// Playback controls
|
||||
document.getElementById('inference-play-btn').addEventListener('click', function() {
|
||||
inferenceState.isPlaying = true;
|
||||
this.disabled = true;
|
||||
document.getElementById('inference-pause-btn').disabled = false;
|
||||
document.getElementById('inference-stop-btn').disabled = false;
|
||||
playInference();
|
||||
});
|
||||
|
||||
document.getElementById('inference-pause-btn').addEventListener('click', function() {
|
||||
inferenceState.isPlaying = false;
|
||||
this.disabled = true;
|
||||
document.getElementById('inference-play-btn').disabled = false;
|
||||
});
|
||||
|
||||
document.getElementById('inference-stop-btn').addEventListener('click', function() {
|
||||
inferenceState.isPlaying = false;
|
||||
inferenceState.currentIndex = 0;
|
||||
document.getElementById('inference-play-btn').disabled = false;
|
||||
document.getElementById('inference-pause-btn').disabled = true;
|
||||
this.disabled = true;
|
||||
resetInferenceDisplay();
|
||||
});
|
||||
|
||||
document.getElementById('inference-speed-select').addEventListener('change', function(e) {
|
||||
inferenceState.speed = parseFloat(e.target.value);
|
||||
});
|
||||
|
||||
function playInference() {
|
||||
if (!inferenceState.isPlaying || inferenceState.currentIndex >= inferenceState.predictions.length) {
|
||||
inferenceState.isPlaying = false;
|
||||
document.getElementById('inference-play-btn').disabled = false;
|
||||
document.getElementById('inference-pause-btn').disabled = true;
|
||||
document.getElementById('inference-stop-btn').disabled = true;
|
||||
return;
|
||||
}
|
||||
|
||||
const prediction = inferenceState.predictions[inferenceState.currentIndex];
|
||||
displayPrediction(prediction);
|
||||
|
||||
inferenceState.currentIndex++;
|
||||
|
||||
// Schedule next prediction
|
||||
const delay = 1000 / inferenceState.speed;
|
||||
setTimeout(playInference, delay);
|
||||
}
|
||||
|
||||
function displayPrediction(prediction) {
|
||||
// Add to timeline table
|
||||
const tbody = document.getElementById('prediction-timeline-body');
|
||||
if (tbody.children[0].colSpan === 5) {
|
||||
tbody.innerHTML = ''; // Clear "no predictions" message
|
||||
}
|
||||
|
||||
const row = document.createElement('tr');
|
||||
const resultClass = prediction.correct ? 'text-success' : 'text-danger';
|
||||
const resultIcon = prediction.correct ? 'fa-check' : 'fa-times';
|
||||
|
||||
row.innerHTML = `
|
||||
<td>${new Date(prediction.timestamp).toLocaleTimeString()}</td>
|
||||
<td><span class="badge bg-${prediction.predicted_action === 'BUY' ? 'success' : 'danger'}">${prediction.predicted_action}</span></td>
|
||||
<td>${(prediction.confidence * 100).toFixed(1)}%</td>
|
||||
<td><span class="badge bg-${prediction.actual_action === 'BUY' ? 'success' : 'danger'}">${prediction.actual_action}</span></td>
|
||||
<td class="${resultClass}"><i class="fas ${resultIcon}"></i></td>
|
||||
`;
|
||||
|
||||
tbody.appendChild(row);
|
||||
|
||||
// Scroll to bottom
|
||||
tbody.parentElement.scrollTop = tbody.parentElement.scrollHeight;
|
||||
|
||||
// Update chart (if implemented)
|
||||
updateInferenceChart(prediction);
|
||||
}
|
||||
|
||||
function updateInferenceChart(prediction) {
|
||||
// TODO: Update Plotly chart with prediction marker
|
||||
}
|
||||
|
||||
function resetInferenceDisplay() {
|
||||
document.getElementById('prediction-timeline-body').innerHTML = `
|
||||
<tr>
|
||||
<td colspan="5" class="text-center text-muted">
|
||||
No predictions yet
|
||||
</td>
|
||||
</tr>
|
||||
`;
|
||||
|
||||
document.getElementById('metric-accuracy').textContent = '--';
|
||||
document.getElementById('metric-precision').textContent = '--';
|
||||
document.getElementById('metric-recall').textContent = '--';
|
||||
document.getElementById('metric-f1').textContent = '--';
|
||||
}
|
||||
|
||||
function updateMetrics(metrics) {
|
||||
document.getElementById('metric-accuracy').textContent = (metrics.accuracy * 100).toFixed(1) + '%';
|
||||
document.getElementById('metric-precision').textContent = (metrics.precision * 100).toFixed(1) + '%';
|
||||
document.getElementById('metric-recall').textContent = (metrics.recall * 100).toFixed(1) + '%';
|
||||
document.getElementById('metric-f1').textContent = (metrics.f1_score * 100).toFixed(1) + '%';
|
||||
|
||||
// Update confusion matrix
|
||||
document.getElementById('cm-tp-buy').textContent = metrics.confusion_matrix.tp_buy;
|
||||
document.getElementById('cm-fn-buy').textContent = metrics.confusion_matrix.fn_buy;
|
||||
document.getElementById('cm-fp-sell').textContent = metrics.confusion_matrix.fp_sell;
|
||||
document.getElementById('cm-tn-sell').textContent = metrics.confusion_matrix.tn_sell;
|
||||
}
|
||||
</script>
|
||||
218
TESTCASES/web/templates/components/training_panel.html
Normal file
218
TESTCASES/web/templates/components/training_panel.html
Normal file
@@ -0,0 +1,218 @@
|
||||
<div class="card training-panel">
|
||||
<div class="card-header">
|
||||
<h6 class="mb-0">
|
||||
<i class="fas fa-graduation-cap"></i>
|
||||
Training
|
||||
</h6>
|
||||
</div>
|
||||
<div class="card-body p-2">
|
||||
<!-- Model Selection -->
|
||||
<div class="mb-3">
|
||||
<label for="model-select" class="form-label small">Model</label>
|
||||
<select class="form-select form-select-sm" id="model-select">
|
||||
<option value="StandardizedCNN">CNN Model</option>
|
||||
<option value="DQN">DQN Agent</option>
|
||||
<option value="Transformer">Transformer</option>
|
||||
</select>
|
||||
</div>
|
||||
|
||||
<!-- Training Controls -->
|
||||
<div class="mb-3">
|
||||
<button class="btn btn-primary btn-sm w-100" id="train-model-btn">
|
||||
<i class="fas fa-play"></i>
|
||||
Train Model
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Training Status -->
|
||||
<div id="training-status" style="display: none;">
|
||||
<div class="alert alert-info py-2 px-2 mb-2">
|
||||
<div class="d-flex align-items-center mb-1">
|
||||
<div class="spinner-border spinner-border-sm me-2" role="status">
|
||||
<span class="visually-hidden">Training...</span>
|
||||
</div>
|
||||
<strong class="small">Training in progress</strong>
|
||||
</div>
|
||||
<div class="progress mb-1" style="height: 10px;">
|
||||
<div class="progress-bar progress-bar-striped progress-bar-animated"
|
||||
id="training-progress-bar"
|
||||
role="progressbar"
|
||||
style="width: 0%"></div>
|
||||
</div>
|
||||
<div class="small">
|
||||
<div>Epoch: <span id="training-epoch">0</span>/<span id="training-total-epochs">0</span></div>
|
||||
<div>Loss: <span id="training-loss">--</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Training Results -->
|
||||
<div id="training-results" style="display: none;">
|
||||
<div class="alert alert-success py-2 px-2 mb-2">
|
||||
<strong class="small">
|
||||
<i class="fas fa-check-circle"></i>
|
||||
Training Complete
|
||||
</strong>
|
||||
<div class="small mt-1">
|
||||
<div>Final Loss: <span id="result-loss">--</span></div>
|
||||
<div>Accuracy: <span id="result-accuracy">--</span></div>
|
||||
<div>Duration: <span id="result-duration">--</span></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Inference Simulation -->
|
||||
<div class="mb-3">
|
||||
<button class="btn btn-secondary btn-sm w-100" id="simulate-inference-btn">
|
||||
<i class="fas fa-brain"></i>
|
||||
Simulate Inference
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<!-- Test Case Stats -->
|
||||
<div class="small text-muted">
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Test Cases:</span>
|
||||
<span id="testcase-count">0</span>
|
||||
</div>
|
||||
<div class="d-flex justify-content-between">
|
||||
<span>Last Training:</span>
|
||||
<span id="last-training-time">Never</span>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script>
|
||||
// Train model button
|
||||
document.getElementById('train-model-btn').addEventListener('click', function() {
|
||||
const modelName = document.getElementById('model-select').value;
|
||||
|
||||
if (appState.annotations.length === 0) {
|
||||
showError('No annotations available for training');
|
||||
return;
|
||||
}
|
||||
|
||||
// Get annotation IDs
|
||||
const annotationIds = appState.annotations.map(a => a.annotation_id);
|
||||
|
||||
// Start training
|
||||
startTraining(modelName, annotationIds);
|
||||
});
|
||||
|
||||
function startTraining(modelName, annotationIds) {
|
||||
// Show training status
|
||||
document.getElementById('training-status').style.display = 'block';
|
||||
document.getElementById('training-results').style.display = 'none';
|
||||
document.getElementById('train-model-btn').disabled = true;
|
||||
|
||||
// Reset progress
|
||||
document.getElementById('training-progress-bar').style.width = '0%';
|
||||
document.getElementById('training-epoch').textContent = '0';
|
||||
document.getElementById('training-loss').textContent = '--';
|
||||
|
||||
// Start training request
|
||||
fetch('/api/train-model', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({
|
||||
model_name: modelName,
|
||||
annotation_ids: annotationIds
|
||||
})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
// Start polling for training progress
|
||||
pollTrainingProgress(data.training_id);
|
||||
} else {
|
||||
showError('Failed to start training: ' + data.error.message);
|
||||
document.getElementById('training-status').style.display = 'none';
|
||||
document.getElementById('train-model-btn').disabled = false;
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
showError('Network error: ' + error.message);
|
||||
document.getElementById('training-status').style.display = 'none';
|
||||
document.getElementById('train-model-btn').disabled = false;
|
||||
});
|
||||
}
|
||||
|
||||
function pollTrainingProgress(trainingId) {
|
||||
const pollInterval = setInterval(function() {
|
||||
fetch('/api/training-progress', {
|
||||
method: 'POST',
|
||||
headers: {'Content-Type': 'application/json'},
|
||||
body: JSON.stringify({training_id: trainingId})
|
||||
})
|
||||
.then(response => response.json())
|
||||
.then(data => {
|
||||
if (data.success) {
|
||||
const progress = data.progress;
|
||||
|
||||
// Update progress bar
|
||||
const percentage = (progress.current_epoch / progress.total_epochs) * 100;
|
||||
document.getElementById('training-progress-bar').style.width = percentage + '%';
|
||||
document.getElementById('training-epoch').textContent = progress.current_epoch;
|
||||
document.getElementById('training-total-epochs').textContent = progress.total_epochs;
|
||||
document.getElementById('training-loss').textContent = progress.current_loss.toFixed(4);
|
||||
|
||||
// Check if complete
|
||||
if (progress.status === 'completed') {
|
||||
clearInterval(pollInterval);
|
||||
showTrainingResults(progress);
|
||||
} else if (progress.status === 'failed') {
|
||||
clearInterval(pollInterval);
|
||||
showError('Training failed: ' + progress.error);
|
||||
document.getElementById('training-status').style.display = 'none';
|
||||
document.getElementById('train-model-btn').disabled = false;
|
||||
}
|
||||
}
|
||||
})
|
||||
.catch(error => {
|
||||
clearInterval(pollInterval);
|
||||
showError('Failed to get training progress: ' + error.message);
|
||||
document.getElementById('training-status').style.display = 'none';
|
||||
document.getElementById('train-model-btn').disabled = false;
|
||||
});
|
||||
}, 1000); // Poll every second
|
||||
}
|
||||
|
||||
function showTrainingResults(results) {
|
||||
// Hide training status
|
||||
document.getElementById('training-status').style.display = 'none';
|
||||
|
||||
// Show results
|
||||
document.getElementById('training-results').style.display = 'block';
|
||||
document.getElementById('result-loss').textContent = results.final_loss.toFixed(4);
|
||||
document.getElementById('result-accuracy').textContent = (results.accuracy * 100).toFixed(2) + '%';
|
||||
document.getElementById('result-duration').textContent = results.duration_seconds.toFixed(1) + 's';
|
||||
|
||||
// Update last training time
|
||||
document.getElementById('last-training-time').textContent = new Date().toLocaleTimeString();
|
||||
|
||||
// Re-enable train button
|
||||
document.getElementById('train-model-btn').disabled = false;
|
||||
|
||||
showSuccess('Training completed successfully');
|
||||
}
|
||||
|
||||
// Simulate inference button
|
||||
document.getElementById('simulate-inference-btn').addEventListener('click', function() {
|
||||
const modelName = document.getElementById('model-select').value;
|
||||
|
||||
if (appState.annotations.length === 0) {
|
||||
showError('No annotations available for inference simulation');
|
||||
return;
|
||||
}
|
||||
|
||||
// Open inference modal
|
||||
const modal = new bootstrap.Modal(document.getElementById('inferenceModal'));
|
||||
modal.show();
|
||||
|
||||
// Start inference simulation
|
||||
if (appState.trainingController) {
|
||||
appState.trainingController.simulateInference(modelName, appState.annotations);
|
||||
}
|
||||
});
|
||||
</script>
|
||||
Reference in New Issue
Block a user