Ryan Malloy bec1606c86 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>
2025-08-11 05:58:27 -06:00

214 lines
7.8 KiB
HTML

{% 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 Timeline</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="card">
<div class="card-header">
<h5>Timeline Events</h5>
<div class="row mt-3">
<div class="col-md-6">
<input type="date" id="startDate" class="form-control" placeholder="Start Date">
</div>
<div class="col-md-6">
<input type="date" id="endDate" class="form-control" placeholder="End Date">
</div>
</div>
</div>
<div class="card-body">
<div id="timeline-container" class="timeline">
<div class="text-center">
<div class="spinner-border" role="status">
<span class="visually-hidden">Loading...</span>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<script>
const projectId = {{ project_id }};
let timelineData = [];
async function loadProjectTimeline() {
try {
const startDate = document.getElementById('startDate').value;
const endDate = document.getElementById('endDate').value;
let url = `/api/projects/${projectId}/timeline`;
const params = new URLSearchParams();
if (startDate) params.append('start_date', startDate);
if (endDate) params.append('end_date', endDate);
if (params.toString()) url += '?' + params.toString();
const response = await fetch(url);
const data = await response.json();
if (response.ok) {
timelineData = data;
displayProjectInfo(data.project);
displayTimeline(data.timeline);
} else {
throw new Error(data.detail || 'Failed to load timeline');
}
} catch (error) {
console.error('Error loading timeline:', error);
document.getElementById('timeline-container').innerHTML = `
<div class="alert alert-danger">
Failed to load timeline: ${error.message}
</div>
`;
}
}
function displayProjectInfo(project) {
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'}<br>
<strong>Total Sessions:</strong> ${project.total_sessions}<br>
<strong>Total Time:</strong> ${project.total_time_minutes} minutes<br>
<strong>Last Activity:</strong> ${new Date(project.last_activity).toLocaleString()}
</p>
</div>
`;
}
function displayTimeline(timeline) {
if (!timeline || timeline.length === 0) {
document.getElementById('timeline-container').innerHTML = `
<div class="alert alert-info">No timeline events found for the selected period.</div>
`;
return;
}
const timelineHtml = timeline.map(event => {
const timestamp = new Date(event.timestamp).toLocaleString();
const typeClass = getEventTypeClass(event.type);
const icon = getEventIcon(event.type);
return `
<div class="timeline-item mb-3">
<div class="row">
<div class="col-md-2 text-end">
<small class="text-muted">${timestamp}</small>
</div>
<div class="col-md-1 text-center">
<span class="badge ${typeClass}">${icon}</span>
</div>
<div class="col-md-9">
<div class="card border-start border-3 border-${typeClass.replace('bg-', '')}">
<div class="card-body py-2">
<h6 class="card-title mb-1">${formatEventTitle(event)}</h6>
<p class="card-text small text-muted mb-0">${formatEventDetails(event)}</p>
</div>
</div>
</div>
</div>
</div>
`;
}).join('');
document.getElementById('timeline-container').innerHTML = timelineHtml;
}
function getEventTypeClass(type) {
const classes = {
'session_start': 'bg-success',
'session_end': 'bg-secondary',
'conversation': 'bg-primary',
'activity': 'bg-warning',
'git_operation': 'bg-info'
};
return classes[type] || 'bg-light';
}
function getEventIcon(type) {
const icons = {
'session_start': '▶️',
'session_end': '⏹️',
'conversation': '💬',
'activity': '🔧',
'git_operation': '📝'
};
return icons[type] || '📌';
}
function formatEventTitle(event) {
switch (event.type) {
case 'session_start':
return `Session Started (${event.data.session_type || 'unknown'})`;
case 'session_end':
return `Session Ended (${event.data.duration_minutes || 0}m)`;
case 'conversation':
return `Conversation: ${event.data.exchange_type || 'unknown'}`;
case 'activity':
return `${event.data.tool_name}: ${event.data.action}`;
case 'git_operation':
return `Git: ${event.data.operation}`;
default:
return event.type;
}
}
function formatEventDetails(event) {
switch (event.type) {
case 'session_start':
return `Branch: ${event.data.git_branch || 'unknown'}, Directory: ${event.data.working_directory || 'unknown'}`;
case 'session_end':
return `Activities: ${event.data.activity_count || 0}, Conversations: ${event.data.conversation_count || 0}`;
case 'conversation':
return event.data.user_prompt || 'No prompt available';
case 'activity':
return `File: ${event.data.file_path || 'unknown'}, Lines: ${event.data.lines_changed || 0}, Success: ${event.data.success}`;
case 'git_operation':
return `${event.data.commit_message || 'No message'} (${event.data.files_changed || 0} files, ${event.data.lines_changed || 0} lines)`;
default:
return JSON.stringify(event.data);
}
}
// Event listeners
document.getElementById('startDate').addEventListener('change', loadProjectTimeline);
document.getElementById('endDate').addEventListener('change', loadProjectTimeline);
// Load initial data
loadProjectTimeline();
</script>
<style>
.timeline-item {
position: relative;
}
.timeline {
padding-left: 1rem;
}
.border-success { border-color: #198754 !important; }
.border-secondary { border-color: #6c757d !important; }
.border-primary { border-color: #0d6efd !important; }
.border-warning { border-color: #ffc107 !important; }
.border-info { border-color: #0dcaf0 !important; }
</style>
{% endblock %}