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:
Ryan Malloy 2025-08-11 05:58:27 -06:00
parent 166247bf70
commit bec1606c86
27 changed files with 6150 additions and 32 deletions

View file

@ -29,6 +29,10 @@
<i class="fas fa-search me-1"></i>
Search
</button>
<button class="btn btn-outline-secondary" type="button" onclick="clearSearch()">
<i class="fas fa-times me-1"></i>
Clear
</button>
</div>
<div class="form-text">
Search through your conversation history with Claude Code
@ -58,9 +62,10 @@
<div class="card-body">
<div id="conversation-results">
<div class="text-center text-muted py-5">
<i class="fas fa-search fa-3x mb-3"></i>
<h5>Search Your Conversations</h5>
<p>Enter a search term to find relevant conversations with Claude.</p>
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading conversations...</span>
</div>
<p class="mt-2">Loading your recent conversations...</p>
</div>
</div>
</div>
@ -73,6 +78,7 @@
<script>
document.addEventListener('DOMContentLoaded', function() {
loadProjects();
loadRecentConversations();
});
async function loadProjects() {
@ -86,17 +92,91 @@ async function loadProjects() {
option.textContent = project.name;
select.appendChild(option);
});
// Add event listener for project filtering
select.addEventListener('change', function() {
const selectedProjectId = this.value ? parseInt(this.value) : null;
loadConversationsForProject(selectedProjectId);
});
} catch (error) {
console.error('Failed to load projects:', error);
}
}
async function loadRecentConversations() {
await loadConversationsForProject(null);
}
async function loadConversationsForProject(projectId = null) {
const resultsContainer = document.getElementById('conversation-results');
const resultsTitle = document.getElementById('results-title');
// Show loading state
resultsContainer.innerHTML = `
<div class="text-center text-muted py-3">
<div class="spinner-border text-primary" role="status">
<span class="visually-hidden">Loading...</span>
</div>
<p class="mt-2">Loading conversations...</p>
</div>
`;
try {
// Update title based on filtering
if (projectId) {
const projects = await apiClient.getProjects();
const project = projects.find(p => p.id === projectId);
resultsTitle.textContent = `Conversations - ${project ? project.name : 'Selected Project'}`;
} else {
resultsTitle.textContent = 'Recent Conversations';
}
const conversations = await apiClient.getConversations(projectId, 50);
if (!conversations.length) {
const noResultsMessage = projectId ?
`No conversations found for this project.` :
`No conversation history has been recorded yet.`;
resultsContainer.innerHTML = `
<div class="text-center text-muted py-5">
<i class="fas fa-comments fa-3x mb-3"></i>
<h5>No Conversations Found</h5>
<p>${noResultsMessage}</p>
${!projectId ? '<p class="small text-muted">Conversations will appear here as you use Claude Code.</p>' : ''}
</div>
`;
return;
}
displaySearchResults(conversations, null);
} catch (error) {
console.error('Failed to load conversations:', error);
resultsContainer.innerHTML = `
<div class="text-center text-danger py-4">
<i class="fas fa-exclamation-triangle fa-2x mb-2"></i>
<p>Failed to load conversations. Please try again.</p>
</div>
`;
}
}
function handleSearchKeypress(event) {
if (event.key === 'Enter') {
searchConversations();
}
}
function clearSearch() {
// Clear the search input
document.getElementById('search-query').value = '';
// Return to filtered/unfiltered conversation list based on current project filter
const projectId = document.getElementById('project-filter').value || null;
loadConversationsForProject(projectId ? parseInt(projectId) : null);
}
async function searchConversations() {
const query = document.getElementById('search-query').value.trim();
const projectId = document.getElementById('project-filter').value || null;
@ -164,9 +244,11 @@ function displaySearchResults(results, query) {
<i class="fas fa-clock me-1"></i>
${ApiUtils.formatRelativeTime(result.timestamp)}
</small>
<span class="badge bg-success ms-2">
${(result.relevance_score * 100).toFixed(0)}% match
</span>
${query ? `
<span class="badge bg-success ms-2">
${(result.relevance_score * 100).toFixed(0)}% match
</span>
` : ''}
</div>
${result.user_prompt ? `
@ -175,7 +257,7 @@ function displaySearchResults(results, query) {
<i class="fas fa-user me-1"></i>
You:
</strong>
<p class="mb-1">${highlightSearchTerms(ApiUtils.truncateText(result.user_prompt, 200), query)}</p>
<p class="mb-1">${query ? highlightSearchTerms(ApiUtils.truncateText(result.user_prompt, 200), query) : ApiUtils.truncateText(result.user_prompt, 200)}</p>
</div>
` : ''}
@ -185,11 +267,11 @@ function displaySearchResults(results, query) {
<i class="fas fa-robot me-1"></i>
Claude:
</strong>
<p class="mb-1">${highlightSearchTerms(ApiUtils.truncateText(result.claude_response, 200), query)}</p>
<p class="mb-1">${query ? highlightSearchTerms(ApiUtils.truncateText(result.claude_response, 200), query) : ApiUtils.truncateText(result.claude_response, 200)}</p>
</div>
` : ''}
${result.context && result.context.length ? `
${result.context && result.context.length && query ? `
<div class="mt-2">
<small class="text-muted">Context snippets:</small>
${result.context.map(snippet => `