anotate ui phase 1

This commit is contained in:
Dobromir Popov
2025-10-18 16:37:13 +03:00
parent d136f9d79c
commit bc7095308a
26 changed files with 3616 additions and 80 deletions

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

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

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

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

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