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

@ -9,6 +9,7 @@ from .conversation import Conversation
from .activity import Activity
from .waiting_period import WaitingPeriod
from .git_operation import GitOperation
from .tool_call import ToolCall
__all__ = [
"Base",
@ -18,4 +19,5 @@ __all__ = [
"Activity",
"WaitingPeriod",
"GitOperation",
"ToolCall",
]

View file

@ -46,7 +46,7 @@ class Activity(Base, TimestampMixin):
file_path: Mapped[Optional[str]] = mapped_column(Text, nullable=True, index=True)
# Metadata and results
metadata: Mapped[Optional[Dict[str, Any]]] = mapped_column(JSON, nullable=True)
activity_metadata: Mapped[Optional[Dict[str, Any]]] = mapped_column(JSON, nullable=True)
success: Mapped[bool] = mapped_column(Boolean, default=True, nullable=False)
error_message: Mapped[Optional[str]] = mapped_column(Text, nullable=True)
@ -145,18 +145,18 @@ class Activity(Base, TimestampMixin):
def get_command_executed(self) -> Optional[str]:
"""Get the command that was executed (for Bash activities)."""
if self.tool_name == "Bash" and self.metadata:
return self.metadata.get("command")
if self.tool_name == "Bash" and self.activity_metadata:
return self.activity_metadata.get("command")
return None
def get_search_pattern(self) -> Optional[str]:
"""Get the search pattern (for Grep activities)."""
if self.tool_name == "Grep" and self.metadata:
return self.metadata.get("pattern")
if self.tool_name == "Grep" and self.activity_metadata:
return self.activity_metadata.get("pattern")
return None
def get_task_type(self) -> Optional[str]:
"""Get the task type (for Task activities)."""
if self.tool_name == "Task" and self.metadata:
return self.metadata.get("task_type")
if self.tool_name == "Task" and self.activity_metadata:
return self.activity_metadata.get("task_type")
return None

View file

@ -36,6 +36,7 @@ class Project(Base, TimestampMixin):
total_time_minutes: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
files_modified_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
lines_changed_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
tool_calls_count: Mapped[int] = mapped_column(Integer, default=0, nullable=False)
# Relationships
sessions: Mapped[List["Session"]] = relationship(
@ -60,10 +61,11 @@ class Project(Base, TimestampMixin):
return self.languages[0]
return None
def update_stats(self, session_duration_minutes: int, files_count: int, lines_count: int) -> None:
def update_stats(self, session_duration_minutes: int, files_count: int, lines_count: int, tool_calls_count: int = 0) -> None:
"""Update project statistics after a session."""
self.total_sessions += 1
self.total_time_minutes += session_duration_minutes
self.files_modified_count += files_count
self.lines_changed_count += lines_count
self.tool_calls_count += tool_calls_count
self.last_session = func.now()

View file

@ -74,6 +74,12 @@ class Session(Base, TimestampMixin):
cascade="all, delete-orphan",
order_by="GitOperation.timestamp"
)
tool_calls: Mapped[List["ToolCall"]] = relationship(
"ToolCall",
back_populates="session",
cascade="all, delete-orphan",
order_by="ToolCall.timestamp"
)
def __repr__(self) -> str:
return f"<Session(id={self.id}, project_id={self.project_id}, type='{self.session_type}')>"
@ -111,10 +117,12 @@ class Session(Base, TimestampMixin):
(activity.lines_added or 0) + (activity.lines_removed or 0)
for activity in self.activities
)
total_tool_calls = len(self.tool_calls)
self.project.update_stats(
session_duration_minutes=self.duration_minutes or 0,
files_count=unique_files,
lines_count=total_lines
lines_count=total_lines,
tool_calls_count=total_tool_calls
)
def add_activity(self) -> None:

33
app/models/tool_call.py Normal file
View file

@ -0,0 +1,33 @@
"""
Tool call tracking model for Claude Code sessions.
"""
from datetime import datetime
from sqlalchemy import Column, Integer, String, Text, DateTime, ForeignKey, Boolean
from sqlalchemy.orm import relationship
from app.models.base import Base
class ToolCall(Base):
"""
Tracks individual tool calls made during Claude Code sessions.
This helps analyze which tools are used most frequently and their success rates.
"""
__tablename__ = "tool_calls"
id = Column(Integer, primary_key=True, index=True)
session_id = Column(String, ForeignKey("sessions.id"), nullable=False, index=True)
tool_name = Column(String(100), nullable=False, index=True)
parameters = Column(Text, nullable=True) # JSON string of tool parameters
result_status = Column(String(20), nullable=True, index=True) # success, error, timeout
error_message = Column(Text, nullable=True)
execution_time_ms = Column(Integer, nullable=True) # Tool execution time in milliseconds
timestamp = Column(DateTime, nullable=False, default=datetime.utcnow, index=True)
# Relationships
session = relationship("Session", back_populates="tool_calls")
def __repr__(self):
return f"<ToolCall(id={self.id}, tool={self.tool_name}, session={self.session_id})>"