This commit is contained in:
Timothy Jaeryang Baek
2026-03-17 17:58:01 -05:00
parent fcf7208352
commit de3317e26b
220 changed files with 17200 additions and 22836 deletions
+57 -75
View File
@@ -53,18 +53,16 @@ class UserAnalyticsResponse(BaseModel):
####################
@router.get("/models", response_model=ModelAnalyticsResponse)
@router.get('/models', response_model=ModelAnalyticsResponse)
async def get_model_analytics(
start_date: Optional[int] = Query(None, description="Start timestamp (epoch)"),
end_date: Optional[int] = Query(None, description="End timestamp (epoch)"),
group_id: Optional[str] = Query(None, description="Filter by user group ID"),
start_date: Optional[int] = Query(None, description='Start timestamp (epoch)'),
end_date: Optional[int] = Query(None, description='End timestamp (epoch)'),
group_id: Optional[str] = Query(None, description='Filter by user group ID'),
user=Depends(get_admin_user),
db: Session = Depends(get_session),
):
"""Get message counts per model."""
counts = ChatMessages.get_message_count_by_model(
start_date=start_date, end_date=end_date, group_id=group_id, db=db
)
counts = ChatMessages.get_message_count_by_model(start_date=start_date, end_date=end_date, group_id=group_id, db=db)
models = [
ModelAnalyticsEntry(model_id=model_id, count=count)
for model_id, count in sorted(counts.items(), key=lambda x: -x[1])
@@ -72,27 +70,23 @@ async def get_model_analytics(
return ModelAnalyticsResponse(models=models)
@router.get("/users", response_model=UserAnalyticsResponse)
@router.get('/users', response_model=UserAnalyticsResponse)
async def get_user_analytics(
start_date: Optional[int] = Query(None, description="Start timestamp (epoch)"),
end_date: Optional[int] = Query(None, description="End timestamp (epoch)"),
group_id: Optional[str] = Query(None, description="Filter by user group ID"),
limit: int = Query(50, description="Max users to return"),
start_date: Optional[int] = Query(None, description='Start timestamp (epoch)'),
end_date: Optional[int] = Query(None, description='End timestamp (epoch)'),
group_id: Optional[str] = Query(None, description='Filter by user group ID'),
limit: int = Query(50, description='Max users to return'),
user=Depends(get_admin_user),
db: Session = Depends(get_session),
):
"""Get message counts and token usage per user with user info."""
counts = ChatMessages.get_message_count_by_user(
start_date=start_date, end_date=end_date, group_id=group_id, db=db
)
counts = ChatMessages.get_message_count_by_user(start_date=start_date, end_date=end_date, group_id=group_id, db=db)
token_usage = ChatMessages.get_token_usage_by_user(
start_date=start_date, end_date=end_date, group_id=group_id, db=db
)
# Get user info for top users
top_user_ids = [
uid for uid, _ in sorted(counts.items(), key=lambda x: -x[1])[:limit]
]
top_user_ids = [uid for uid, _ in sorted(counts.items(), key=lambda x: -x[1])[:limit]]
user_info = {u.id: u for u in Users.get_users_by_user_ids(top_user_ids, db=db)}
users = []
@@ -105,22 +99,22 @@ async def get_user_analytics(
name=u.name if u else None,
email=u.email if u else None,
count=counts[user_id],
input_tokens=tokens.get("input_tokens", 0),
output_tokens=tokens.get("output_tokens", 0),
total_tokens=tokens.get("total_tokens", 0),
input_tokens=tokens.get('input_tokens', 0),
output_tokens=tokens.get('output_tokens', 0),
total_tokens=tokens.get('total_tokens', 0),
)
)
return UserAnalyticsResponse(users=users)
@router.get("/messages", response_model=list[ChatMessageModel])
@router.get('/messages', response_model=list[ChatMessageModel])
async def get_messages(
model_id: Optional[str] = Query(None, description="Filter by model ID"),
user_id: Optional[str] = Query(None, description="Filter by user ID"),
chat_id: Optional[str] = Query(None, description="Filter by chat ID"),
start_date: Optional[int] = Query(None, description="Start timestamp (epoch)"),
end_date: Optional[int] = Query(None, description="End timestamp (epoch)"),
model_id: Optional[str] = Query(None, description='Filter by model ID'),
user_id: Optional[str] = Query(None, description='Filter by user ID'),
chat_id: Optional[str] = Query(None, description='Filter by chat ID'),
start_date: Optional[int] = Query(None, description='Start timestamp (epoch)'),
end_date: Optional[int] = Query(None, description='End timestamp (epoch)'),
skip: int = Query(0),
limit: int = Query(50, le=100),
user=Depends(get_admin_user),
@@ -139,9 +133,7 @@ async def get_messages(
db=db,
)
elif user_id:
return ChatMessages.get_messages_by_user_id(
user_id=user_id, skip=skip, limit=limit, db=db
)
return ChatMessages.get_messages_by_user_id(user_id=user_id, skip=skip, limit=limit, db=db)
else:
# Return empty if no filter specified
return []
@@ -154,11 +146,11 @@ class SummaryResponse(BaseModel):
total_users: int
@router.get("/summary", response_model=SummaryResponse)
@router.get('/summary', response_model=SummaryResponse)
async def get_summary(
start_date: Optional[int] = Query(None, description="Start timestamp (epoch)"),
end_date: Optional[int] = Query(None, description="End timestamp (epoch)"),
group_id: Optional[str] = Query(None, description="Filter by user group ID"),
start_date: Optional[int] = Query(None, description='Start timestamp (epoch)'),
end_date: Optional[int] = Query(None, description='End timestamp (epoch)'),
group_id: Optional[str] = Query(None, description='Filter by user group ID'),
user=Depends(get_admin_user),
db: Session = Depends(get_session),
):
@@ -190,29 +182,24 @@ class DailyStatsResponse(BaseModel):
data: list[DailyStatsEntry]
@router.get("/daily", response_model=DailyStatsResponse)
@router.get('/daily', response_model=DailyStatsResponse)
async def get_daily_stats(
start_date: Optional[int] = Query(None, description="Start timestamp (epoch)"),
end_date: Optional[int] = Query(None, description="End timestamp (epoch)"),
group_id: Optional[str] = Query(None, description="Filter by user group ID"),
granularity: str = Query("daily", description="Granularity: 'hourly' or 'daily'"),
start_date: Optional[int] = Query(None, description='Start timestamp (epoch)'),
end_date: Optional[int] = Query(None, description='End timestamp (epoch)'),
group_id: Optional[str] = Query(None, description='Filter by user group ID'),
granularity: str = Query('daily', description="Granularity: 'hourly' or 'daily'"),
user=Depends(get_admin_user),
db: Session = Depends(get_session),
):
"""Get message counts grouped by model for time-series chart."""
if granularity == "hourly":
counts = ChatMessages.get_hourly_message_counts_by_model(
start_date=start_date, end_date=end_date, db=db
)
if granularity == 'hourly':
counts = ChatMessages.get_hourly_message_counts_by_model(start_date=start_date, end_date=end_date, db=db)
else:
counts = ChatMessages.get_daily_message_counts_by_model(
start_date=start_date, end_date=end_date, group_id=group_id, db=db
)
return DailyStatsResponse(
data=[
DailyStatsEntry(date=date, models=models)
for date, models in sorted(counts.items())
]
data=[DailyStatsEntry(date=date, models=models) for date, models in sorted(counts.items())]
)
@@ -231,22 +218,20 @@ class TokenUsageResponse(BaseModel):
total_tokens: int
@router.get("/tokens", response_model=TokenUsageResponse)
@router.get('/tokens', response_model=TokenUsageResponse)
async def get_token_usage(
start_date: Optional[int] = Query(None),
end_date: Optional[int] = Query(None),
group_id: Optional[str] = Query(None, description="Filter by user group ID"),
group_id: Optional[str] = Query(None, description='Filter by user group ID'),
user=Depends(get_admin_user),
db: Session = Depends(get_session),
):
"""Get token usage aggregated by model."""
usage = ChatMessages.get_token_usage_by_model(
start_date=start_date, end_date=end_date, group_id=group_id, db=db
)
usage = ChatMessages.get_token_usage_by_model(start_date=start_date, end_date=end_date, group_id=group_id, db=db)
models = [
TokenUsageEntry(model_id=model_id, **data)
for model_id, data in sorted(usage.items(), key=lambda x: -x[1]["total_tokens"])
for model_id, data in sorted(usage.items(), key=lambda x: -x[1]['total_tokens'])
]
total_input = sum(m.input_tokens for m in models)
@@ -278,7 +263,7 @@ class ModelChatsResponse(BaseModel):
total: int
@router.get("/models/{model_id:path}/chats", response_model=ModelChatsResponse)
@router.get('/models/{model_id:path}/chats', response_model=ModelChatsResponse)
async def get_model_chats(
model_id: str,
start_date: Optional[int] = Query(None),
@@ -311,7 +296,7 @@ async def get_model_chats(
continue
# Get user_id from first user message
first_user_msg = next((m for m in messages if m.role == "user"), None)
first_user_msg = next((m for m in messages if m.role == 'user'), None)
user_id = first_user_msg.user_id if first_user_msg else None
# Extract first message content as preview
@@ -321,8 +306,8 @@ async def get_model_chats(
if isinstance(content, str):
first_message = content[:200]
elif isinstance(content, list):
text_parts = [b.get("text", "") for b in content if isinstance(b, dict)]
first_message = " ".join(text_parts)[:200]
text_parts = [b.get('text', '') for b in content if isinstance(b, dict)]
first_message = ' '.join(text_parts)[:200]
# Get user info
user_name = None
@@ -367,10 +352,10 @@ class ModelOverviewResponse(BaseModel):
tags: list[TagEntry]
@router.get("/models/{model_id:path}/overview", response_model=ModelOverviewResponse)
@router.get('/models/{model_id:path}/overview', response_model=ModelOverviewResponse)
async def get_model_overview(
model_id: str,
days: int = Query(30, description="Number of days of history (0 for all)"),
days: int = Query(30, description='Number of days of history (0 for all)'),
user=Depends(get_admin_user),
db: Session = Depends(get_session),
):
@@ -387,7 +372,7 @@ async def get_model_overview(
)
# Get feedback history per day
history_counts: dict[str, dict] = defaultdict(lambda: {"won": 0, "lost": 0})
history_counts: dict[str, dict] = defaultdict(lambda: {'won': 0, 'lost': 0})
# Calculate start date for history
now = datetime.now()
@@ -398,19 +383,19 @@ async def get_model_overview(
for chat_id in chat_ids:
feedbacks = Feedbacks.get_feedbacks_by_chat_id(chat_id, db=db)
for fb in feedbacks:
if fb.data and "rating" in fb.data:
rating = fb.data["rating"]
if fb.data and 'rating' in fb.data:
rating = fb.data['rating']
fb_date = datetime.fromtimestamp(fb.created_at)
# Filter by date range
if start_dt and fb_date < start_dt:
continue
date_str = fb_date.strftime("%Y-%m-%d")
date_str = fb_date.strftime('%Y-%m-%d')
if rating == 1:
history_counts[date_str]["won"] += 1
history_counts[date_str]['won'] += 1
elif rating == -1:
history_counts[date_str]["lost"] += 1
history_counts[date_str]['lost'] += 1
# Fill in missing days
history = []
@@ -421,18 +406,18 @@ async def get_model_overview(
elif history_counts:
# Find earliest date
min_date = min(history_counts.keys())
current = datetime.strptime(min_date, "%Y-%m-%d")
current = datetime.strptime(min_date, '%Y-%m-%d')
else:
current = now
while current <= end_dt:
date_str = current.strftime("%Y-%m-%d")
counts = history_counts.get(date_str, {"won": 0, "lost": 0})
date_str = current.strftime('%Y-%m-%d')
counts = history_counts.get(date_str, {'won': 0, 'lost': 0})
history.append(
HistoryEntry(
date=date_str,
won=counts["won"],
lost=counts["lost"],
won=counts['won'],
lost=counts['lost'],
)
)
current += timedelta(days=1)
@@ -442,13 +427,10 @@ async def get_model_overview(
for chat_id in chat_ids:
chat = Chats.get_chat_by_id(chat_id, db=db)
if chat and chat.meta:
for tag in chat.meta.get("tags", []):
for tag in chat.meta.get('tags', []):
tag_counts[tag] += 1
# Sort by count and take top 10
tags = [
TagEntry(tag=tag, count=count)
for tag, count in sorted(tag_counts.items(), key=lambda x: -x[1])[:10]
]
tags = [TagEntry(tag=tag, count=count) for tag, count in sorted(tag_counts.items(), key=lambda x: -x[1])[:10]]
return ModelOverviewResponse(history=history, tags=tags)