Add comprehensive development intelligence system that tracks: - Development sessions with automatic start/stop - Full conversation history with semantic search - Tool usage and file operation analytics - Think time and engagement analysis - Git activity correlation - Learning pattern recognition - Productivity insights and metrics Features: - FastAPI backend with SQLite database - Modern web dashboard with interactive charts - Claude Code hook integration for automatic tracking - Comprehensive test suite with 100+ tests - Complete API documentation (OpenAPI/Swagger) - Privacy-first design with local data storage 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
390 lines
12 KiB
HTML
390 lines
12 KiB
HTML
{% extends "base.html" %}
|
|
|
|
{% block title %}Analytics - Claude Code Project Tracker{% endblock %}
|
|
|
|
{% block content %}
|
|
<div class="row">
|
|
<div class="col-12">
|
|
<div class="d-flex justify-content-between align-items-center mb-4">
|
|
<h1>
|
|
<i class="fas fa-chart-line me-2"></i>
|
|
Development Analytics
|
|
</h1>
|
|
<div class="btn-group" role="group">
|
|
<button type="button" class="btn btn-outline-primary active" onclick="setTimePeriod(30)">
|
|
30 Days
|
|
</button>
|
|
<button type="button" class="btn btn-outline-primary" onclick="setTimePeriod(7)">
|
|
7 Days
|
|
</button>
|
|
<button type="button" class="btn btn-outline-primary" onclick="setTimePeriod(90)">
|
|
90 Days
|
|
</button>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Productivity Metrics -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-brain me-2"></i>
|
|
Engagement Analysis
|
|
</h6>
|
|
</div>
|
|
<div class="card-body text-center">
|
|
<div class="display-6 text-primary mb-2" id="engagement-metric">-</div>
|
|
<p class="text-muted">Engagement Score</p>
|
|
<canvas id="engagementChart" height="150"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-clock me-2"></i>
|
|
Time Analysis
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div class="mb-3">
|
|
<div class="d-flex justify-content-between">
|
|
<span>Average Session</span>
|
|
<strong id="avg-session">-</strong>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<div class="d-flex justify-content-between">
|
|
<span>Think Time</span>
|
|
<strong id="think-time-metric">-</strong>
|
|
</div>
|
|
</div>
|
|
<div class="mb-3">
|
|
<div class="d-flex justify-content-between">
|
|
<span>Files per Session</span>
|
|
<strong id="files-per-session">-</strong>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-graduation-cap me-2"></i>
|
|
Learning Insights
|
|
</h6>
|
|
</div>
|
|
<div class="card-body" id="learning-insights">
|
|
<div class="text-center py-3">
|
|
<div class="spinner-border spinner-border-sm text-primary"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Development Patterns -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-8">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-chart-area me-2"></i>
|
|
Development Patterns
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="patternsChart" height="100"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-4">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-user-clock me-2"></i>
|
|
Working Hours
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<canvas id="hoursChart" height="150"></canvas>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Tool Usage & Git Activity -->
|
|
<div class="row mb-4">
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fas fa-tools me-2"></i>
|
|
Tool Usage Statistics
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="tool-stats">
|
|
<div class="text-center py-3">
|
|
<div class="spinner-border spinner-border-sm text-primary"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<div class="col-md-6">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h6 class="mb-0">
|
|
<i class="fab fa-git-alt me-2"></i>
|
|
Git Activity
|
|
</h6>
|
|
</div>
|
|
<div class="card-body">
|
|
<div id="git-stats">
|
|
<div class="text-center py-3">
|
|
<div class="spinner-border spinner-border-sm text-primary"></div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
{% endblock %}
|
|
|
|
{% block scripts %}
|
|
<script>
|
|
let currentTimePeriod = 30;
|
|
let engagementChart = null;
|
|
let patternsChart = null;
|
|
let hoursChart = null;
|
|
|
|
document.addEventListener('DOMContentLoaded', function() {
|
|
loadAnalyticsData();
|
|
});
|
|
|
|
function setTimePeriod(days) {
|
|
currentTimePeriod = days;
|
|
|
|
// Update active button
|
|
document.querySelectorAll('.btn-group .btn').forEach(btn => {
|
|
btn.classList.remove('active');
|
|
});
|
|
event.target.classList.add('active');
|
|
|
|
loadAnalyticsData();
|
|
}
|
|
|
|
async function loadAnalyticsData() {
|
|
try {
|
|
const [
|
|
productivityMetrics,
|
|
developmentPatterns,
|
|
learningInsights,
|
|
toolUsageStats,
|
|
gitStats
|
|
] = await Promise.all([
|
|
apiClient.getProductivityMetrics(null, currentTimePeriod),
|
|
apiClient.getDevelopmentPatterns(null, currentTimePeriod),
|
|
apiClient.getLearningInsights(null, currentTimePeriod),
|
|
apiClient.getToolUsageStats(null, null, currentTimePeriod),
|
|
apiClient.getGitActivityStats(null, currentTimePeriod)
|
|
]);
|
|
|
|
updateProductivityMetrics(productivityMetrics);
|
|
updateDevelopmentPatterns(developmentPatterns);
|
|
updateLearningInsights(learningInsights);
|
|
updateToolStats(toolUsageStats);
|
|
updateGitStats(gitStats);
|
|
|
|
} catch (error) {
|
|
console.error('Failed to load analytics data:', error);
|
|
}
|
|
}
|
|
|
|
function updateProductivityMetrics(metrics) {
|
|
document.getElementById('engagement-metric').textContent =
|
|
metrics.engagement_score?.toFixed(0) || '-';
|
|
|
|
document.getElementById('avg-session').textContent =
|
|
ApiUtils.formatDuration(metrics.average_session_length || 0);
|
|
|
|
document.getElementById('think-time-metric').textContent =
|
|
`${(metrics.think_time_average || 0).toFixed(1)}s`;
|
|
|
|
document.getElementById('files-per-session').textContent =
|
|
(metrics.files_per_session || 0).toFixed(1);
|
|
}
|
|
|
|
function updateDevelopmentPatterns(patterns) {
|
|
if (patterns.working_hours) {
|
|
updateWorkingHoursChart(patterns.working_hours);
|
|
}
|
|
|
|
if (patterns.productivity_trends) {
|
|
updatePatternsChart(patterns);
|
|
}
|
|
}
|
|
|
|
function updateWorkingHoursChart(workingHours) {
|
|
const ctx = document.getElementById('hoursChart').getContext('2d');
|
|
|
|
if (hoursChart) {
|
|
hoursChart.destroy();
|
|
}
|
|
|
|
const hours = Array.from({length: 24}, (_, i) => i);
|
|
const data = hours.map(hour => workingHours.distribution[hour] || 0);
|
|
|
|
hoursChart = new Chart(ctx, {
|
|
type: 'bar',
|
|
data: {
|
|
labels: hours.map(h => `${h}:00`),
|
|
datasets: [{
|
|
data: data,
|
|
backgroundColor: '#007bff',
|
|
borderRadius: 4
|
|
}]
|
|
},
|
|
options: {
|
|
responsive: true,
|
|
maintainAspectRatio: false,
|
|
plugins: {
|
|
legend: { display: false }
|
|
},
|
|
scales: {
|
|
x: {
|
|
ticks: { maxTicksLimit: 8 }
|
|
},
|
|
y: {
|
|
beginAtZero: true
|
|
}
|
|
}
|
|
}
|
|
});
|
|
}
|
|
|
|
function updateLearningInsights(insights) {
|
|
const container = document.getElementById('learning-insights');
|
|
|
|
if (insights.message) {
|
|
container.innerHTML = `
|
|
<div class="text-center text-muted">
|
|
<i class="fas fa-info-circle mb-2"></i>
|
|
<p class="small">${insights.message}</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
const topTopics = Object.entries(insights.learning_topics?.frequency || {})
|
|
.sort(([,a], [,b]) => b - a)
|
|
.slice(0, 3);
|
|
|
|
const insightsHtml = `
|
|
<div class="mb-3">
|
|
<small class="text-muted">Most Discussed Topics</small>
|
|
${topTopics.map(([topic, count]) => `
|
|
<div class="d-flex justify-content-between align-items-center mt-1">
|
|
<span class="small">${topic}</span>
|
|
<span class="badge bg-primary">${count}</span>
|
|
</div>
|
|
`).join('')}
|
|
</div>
|
|
<div class="text-center">
|
|
<div class="fw-bold text-success">${insights.insights?.diverse_learning || 0}</div>
|
|
<small class="text-muted">Topics Explored</small>
|
|
</div>
|
|
`;
|
|
|
|
container.innerHTML = insightsHtml;
|
|
}
|
|
|
|
function updateToolStats(toolStats) {
|
|
const container = document.getElementById('tool-stats');
|
|
|
|
if (!toolStats.length) {
|
|
container.innerHTML = `
|
|
<div class="text-center text-muted py-3">
|
|
<i class="fas fa-tools mb-2"></i>
|
|
<p class="small">No tool usage data</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
const topTools = toolStats.slice(0, 5);
|
|
const maxUsage = Math.max(...topTools.map(t => t.usage_count));
|
|
|
|
const toolsHtml = topTools.map(tool => {
|
|
const percentage = (tool.usage_count / maxUsage) * 100;
|
|
return `
|
|
<div class="mb-3">
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<span class="small">
|
|
<i class="${ApiUtils.getToolIcon(tool.tool_name)} me-1"></i>
|
|
${tool.tool_name}
|
|
</span>
|
|
<span class="badge bg-${ApiUtils.getToolColor(tool.tool_name)}">${tool.usage_count}</span>
|
|
</div>
|
|
<div class="progress" style="height: 6px;">
|
|
<div class="progress-bar bg-${ApiUtils.getToolColor(tool.tool_name)}"
|
|
style="width: ${percentage}%"></div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
}).join('');
|
|
|
|
container.innerHTML = toolsHtml;
|
|
}
|
|
|
|
function updateGitStats(gitStats) {
|
|
const container = document.getElementById('git-stats');
|
|
|
|
if (!gitStats.total_operations) {
|
|
container.innerHTML = `
|
|
<div class="text-center text-muted py-3">
|
|
<i class="fab fa-git-alt mb-2"></i>
|
|
<p class="small">No git activity data</p>
|
|
</div>
|
|
`;
|
|
return;
|
|
}
|
|
|
|
const statsHtml = `
|
|
<div class="row g-3 text-center">
|
|
<div class="col-6">
|
|
<div class="fw-bold text-primary">${gitStats.total_operations}</div>
|
|
<small class="text-muted">Operations</small>
|
|
</div>
|
|
<div class="col-6">
|
|
<div class="fw-bold text-success">${gitStats.success_rate || 0}%</div>
|
|
<small class="text-muted">Success Rate</small>
|
|
</div>
|
|
</div>
|
|
<hr>
|
|
<div class="mb-2">
|
|
<small class="text-muted">Operation Types</small>
|
|
</div>
|
|
${Object.entries(gitStats.operations_by_type || {}).map(([type, count]) => `
|
|
<div class="d-flex justify-content-between align-items-center mb-1">
|
|
<span class="small">${type}</span>
|
|
<span class="badge bg-secondary">${count}</span>
|
|
</div>
|
|
`).join('')}
|
|
`;
|
|
|
|
container.innerHTML = statsHtml;
|
|
}
|
|
</script>
|
|
{% endblock %} |