Add comprehensive documentation system and tool call tracking
## Documentation System - Create complete documentation hub at /dashboard/docs with: - Getting Started guide with quick setup and troubleshooting - Hook Setup Guide with platform-specific configurations - API Reference with all endpoints and examples - FAQ with searchable questions and categories - Add responsive design with interactive features - Update navigation in base template ## Tool Call Tracking - Add ToolCall model for tracking Claude Code tool usage - Create /api/tool-calls endpoints for recording and analytics - Add tool_call hook type with auto-session detection - Include tool calls in project statistics and recalculation - Track tool names, parameters, execution time, and success rates ## Project Enhancements - Add project timeline and statistics pages (fix 404 errors) - Create recalculation script for fixing zero statistics - Update project stats to include tool call counts - Enhance session model with tool call relationships ## Infrastructure - Switch from requirements.txt to pyproject.toml/uv.lock - Add data import functionality for claude.json files - Update database connection to include all new models - Add comprehensive API documentation 🤖 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
166247bf70
commit
bec1606c86
27 changed files with 6150 additions and 32 deletions
353
app/dashboard/templates/project_stats.html
Normal file
353
app/dashboard/templates/project_stats.html
Normal file
|
|
@ -0,0 +1,353 @@
|
|||
{% extends "base.html" %}
|
||||
|
||||
{% block title %}{{ title }}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
<div class="col-md-12">
|
||||
<div class="d-flex justify-content-between align-items-center mb-4">
|
||||
<h2>Project Statistics</h2>
|
||||
<a href="/dashboard/projects" class="btn btn-outline-secondary">← Back to Projects</a>
|
||||
</div>
|
||||
|
||||
<div id="project-info" class="card mb-4">
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">Loading project information...</h5>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Time Period</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<select id="dayRange" class="form-select">
|
||||
<option value="7">Last 7 days</option>
|
||||
<option value="30" selected>Last 30 days</option>
|
||||
<option value="90">Last 90 days</option>
|
||||
<option value="365">Last year</option>
|
||||
<option value="0">All time</option>
|
||||
</select>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Summary</h5>
|
||||
</div>
|
||||
<div class="card-body" id="summary-stats">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Session Statistics</h5>
|
||||
</div>
|
||||
<div class="card-body" id="session-stats">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Activity Breakdown</h5>
|
||||
</div>
|
||||
<div class="card-body" id="activity-stats">
|
||||
Loading...
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Tool Usage</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="toolsChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-6 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Language Usage</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="languagesChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row">
|
||||
<div class="col-md-12 mb-4">
|
||||
<div class="card">
|
||||
<div class="card-header">
|
||||
<h5>Productivity Trends</h5>
|
||||
</div>
|
||||
<div class="card-body">
|
||||
<canvas id="trendsChart"></canvas>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
|
||||
<script>
|
||||
const projectId = {{ project_id }};
|
||||
let statsData = {};
|
||||
let charts = {};
|
||||
|
||||
async function loadProjectStats() {
|
||||
try {
|
||||
const days = document.getElementById('dayRange').value;
|
||||
const response = await fetch(`/api/projects/${projectId}/stats?days=${days}`);
|
||||
const data = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
statsData = data;
|
||||
displayStats();
|
||||
updateCharts();
|
||||
} else {
|
||||
throw new Error(data.detail || 'Failed to load stats');
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading stats:', error);
|
||||
document.getElementById('summary-stats').innerHTML = `
|
||||
<div class="alert alert-danger">
|
||||
Failed to load statistics: ${error.message}
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
}
|
||||
|
||||
async function loadProjectInfo() {
|
||||
try {
|
||||
const response = await fetch(`/api/projects/${projectId}`);
|
||||
const project = await response.json();
|
||||
|
||||
if (response.ok) {
|
||||
document.getElementById('project-info').innerHTML = `
|
||||
<div class="card-body">
|
||||
<h5 class="card-title">${project.name}</h5>
|
||||
<p class="card-text">
|
||||
<strong>Path:</strong> ${project.path}<br>
|
||||
<strong>Git Repository:</strong> ${project.git_repo || 'Not a git repo'}<br>
|
||||
<strong>Languages:</strong> ${project.languages ? project.languages.join(', ') : 'Unknown'}
|
||||
</p>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Error loading project info:', error);
|
||||
}
|
||||
}
|
||||
|
||||
function displayStats() {
|
||||
// Summary stats
|
||||
document.getElementById('summary-stats').innerHTML = `
|
||||
<div class="row text-center">
|
||||
<div class="col-md-3">
|
||||
<h4 class="text-primary">${statsData.session_statistics.total_sessions}</h4>
|
||||
<small>Sessions</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h4 class="text-success">${Math.round(statsData.session_statistics.total_time_minutes / 60 * 10) / 10}h</h4>
|
||||
<small>Total Time</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h4 class="text-warning">${statsData.activity_statistics.total}</h4>
|
||||
<small>Activities</small>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<h4 class="text-info">${statsData.conversation_statistics.total}</h4>
|
||||
<small>Conversations</small>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
// Session stats
|
||||
document.getElementById('session-stats').innerHTML = `
|
||||
<p><strong>Total Sessions:</strong> ${statsData.session_statistics.total_sessions}</p>
|
||||
<p><strong>Total Time:</strong> ${Math.round(statsData.session_statistics.total_time_minutes / 60 * 10) / 10} hours</p>
|
||||
<p><strong>Average Session:</strong> ${statsData.session_statistics.average_session_length_minutes} minutes</p>
|
||||
<p><strong>Daily Average Time:</strong> ${statsData.summary.daily_average_time} minutes</p>
|
||||
<p><strong>Daily Average Sessions:</strong> ${statsData.summary.daily_average_sessions}</p>
|
||||
`;
|
||||
|
||||
// Activity stats
|
||||
const topTools = Object.entries(statsData.activity_statistics.by_tool)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 5)
|
||||
.map(([tool, count]) => `<li>${tool}: ${count}</li>`)
|
||||
.join('');
|
||||
|
||||
const topLangs = Object.entries(statsData.activity_statistics.by_language)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 5)
|
||||
.map(([lang, count]) => `<li>${lang}: ${count}</li>`)
|
||||
.join('');
|
||||
|
||||
document.getElementById('activity-stats').innerHTML = `
|
||||
<p><strong>Total Activities:</strong> ${statsData.activity_statistics.total}</p>
|
||||
<p><strong>Most Used Tool:</strong> ${statsData.summary.most_used_tool || 'None'}</p>
|
||||
<p><strong>Primary Language:</strong> ${statsData.summary.primary_language || 'None'}</p>
|
||||
<div class="row mt-3">
|
||||
<div class="col-md-6">
|
||||
<strong>Top Tools:</strong>
|
||||
<ul class="small">${topTools || '<li>No data</li>'}</ul>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
<strong>Languages:</strong>
|
||||
<ul class="small">${topLangs || '<li>No data</li>'}</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
}
|
||||
|
||||
function updateCharts() {
|
||||
// Destroy existing charts
|
||||
Object.values(charts).forEach(chart => chart.destroy());
|
||||
charts = {};
|
||||
|
||||
// Tools chart
|
||||
const toolData = Object.entries(statsData.activity_statistics.by_tool)
|
||||
.sort((a, b) => b[1] - a[1])
|
||||
.slice(0, 10);
|
||||
|
||||
if (toolData.length > 0) {
|
||||
charts.tools = new Chart(document.getElementById('toolsChart'), {
|
||||
type: 'doughnut',
|
||||
data: {
|
||||
labels: toolData.map(([tool]) => tool),
|
||||
datasets: [{
|
||||
data: toolData.map(([, count]) => count),
|
||||
backgroundColor: [
|
||||
'#FF6384', '#36A2EB', '#FFCE56', '#4BC0C0', '#9966FF',
|
||||
'#FF9F40', '#C9CBCF', '#4BC0C0', '#FF6384', '#36A2EB'
|
||||
]
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Languages chart
|
||||
const langData = Object.entries(statsData.activity_statistics.by_language)
|
||||
.sort((a, b) => b[1] - a[1]);
|
||||
|
||||
if (langData.length > 0) {
|
||||
charts.languages = new Chart(document.getElementById('languagesChart'), {
|
||||
type: 'bar',
|
||||
data: {
|
||||
labels: langData.map(([lang]) => lang),
|
||||
datasets: [{
|
||||
label: 'Activities',
|
||||
data: langData.map(([, count]) => count),
|
||||
backgroundColor: '#36A2EB'
|
||||
}]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
scales: {
|
||||
y: {
|
||||
beginAtZero: true
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// Trends chart
|
||||
if (statsData.productivity_trends && statsData.productivity_trends.length > 0) {
|
||||
charts.trends = new Chart(document.getElementById('trendsChart'), {
|
||||
type: 'line',
|
||||
data: {
|
||||
labels: statsData.productivity_trends.map(day => day.date),
|
||||
datasets: [
|
||||
{
|
||||
label: 'Sessions',
|
||||
data: statsData.productivity_trends.map(day => day.sessions),
|
||||
borderColor: '#FF6384',
|
||||
backgroundColor: 'rgba(255, 99, 132, 0.1)',
|
||||
yAxisID: 'y'
|
||||
},
|
||||
{
|
||||
label: 'Time (minutes)',
|
||||
data: statsData.productivity_trends.map(day => day.time_minutes),
|
||||
borderColor: '#36A2EB',
|
||||
backgroundColor: 'rgba(54, 162, 235, 0.1)',
|
||||
yAxisID: 'y1'
|
||||
}
|
||||
]
|
||||
},
|
||||
options: {
|
||||
responsive: true,
|
||||
maintainAspectRatio: true,
|
||||
interaction: {
|
||||
mode: 'index',
|
||||
intersect: false,
|
||||
},
|
||||
scales: {
|
||||
x: {
|
||||
display: true,
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Date'
|
||||
}
|
||||
},
|
||||
y: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'left',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Sessions'
|
||||
}
|
||||
},
|
||||
y1: {
|
||||
type: 'linear',
|
||||
display: true,
|
||||
position: 'right',
|
||||
title: {
|
||||
display: true,
|
||||
text: 'Time (minutes)'
|
||||
},
|
||||
grid: {
|
||||
drawOnChartArea: false,
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// Event listeners
|
||||
document.getElementById('dayRange').addEventListener('change', loadProjectStats);
|
||||
|
||||
// Load initial data
|
||||
loadProjectInfo();
|
||||
loadProjectStats();
|
||||
</script>
|
||||
{% endblock %}
|
||||
Loading…
Add table
Add a link
Reference in a new issue