enh: analytics
This commit is contained in:
@@ -312,10 +312,12 @@ class ChatMessageTable:
|
|||||||
self,
|
self,
|
||||||
start_date: Optional[int] = None,
|
start_date: Optional[int] = None,
|
||||||
end_date: Optional[int] = None,
|
end_date: Optional[int] = None,
|
||||||
|
group_id: Optional[str] = None,
|
||||||
db: Optional[Session] = None,
|
db: Optional[Session] = None,
|
||||||
) -> dict[str, int]:
|
) -> dict[str, int]:
|
||||||
with get_db_context(db) as db:
|
with get_db_context(db) as db:
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
from open_webui.models.groups import GroupMember
|
||||||
|
|
||||||
query = db.query(
|
query = db.query(
|
||||||
ChatMessage.model_id, func.count(ChatMessage.id).label("count")
|
ChatMessage.model_id, func.count(ChatMessage.id).label("count")
|
||||||
@@ -329,6 +331,9 @@ class ChatMessageTable:
|
|||||||
query = query.filter(ChatMessage.created_at >= start_date)
|
query = query.filter(ChatMessage.created_at >= start_date)
|
||||||
if end_date:
|
if end_date:
|
||||||
query = query.filter(ChatMessage.created_at <= end_date)
|
query = query.filter(ChatMessage.created_at <= end_date)
|
||||||
|
if group_id:
|
||||||
|
group_users = db.query(GroupMember.user_id).filter(GroupMember.group_id == group_id).subquery()
|
||||||
|
query = query.filter(ChatMessage.user_id.in_(group_users))
|
||||||
|
|
||||||
results = query.group_by(ChatMessage.model_id).all()
|
results = query.group_by(ChatMessage.model_id).all()
|
||||||
return {row.model_id: row.count for row in results}
|
return {row.model_id: row.count for row in results}
|
||||||
@@ -337,11 +342,13 @@ class ChatMessageTable:
|
|||||||
self,
|
self,
|
||||||
start_date: Optional[int] = None,
|
start_date: Optional[int] = None,
|
||||||
end_date: Optional[int] = None,
|
end_date: Optional[int] = None,
|
||||||
|
group_id: Optional[str] = None,
|
||||||
db: Optional[Session] = None,
|
db: Optional[Session] = None,
|
||||||
) -> dict[str, dict]:
|
) -> dict[str, dict]:
|
||||||
"""Aggregate token usage by model using database-level aggregation."""
|
"""Aggregate token usage by model using database-level aggregation."""
|
||||||
with get_db_context(db) as db:
|
with get_db_context(db) as db:
|
||||||
from sqlalchemy import func, cast, Integer
|
from sqlalchemy import func, cast, Integer
|
||||||
|
from open_webui.models.groups import GroupMember
|
||||||
|
|
||||||
dialect = db.bind.dialect.name
|
dialect = db.bind.dialect.name
|
||||||
|
|
||||||
@@ -379,6 +386,9 @@ class ChatMessageTable:
|
|||||||
query = query.filter(ChatMessage.created_at >= start_date)
|
query = query.filter(ChatMessage.created_at >= start_date)
|
||||||
if end_date:
|
if end_date:
|
||||||
query = query.filter(ChatMessage.created_at <= end_date)
|
query = query.filter(ChatMessage.created_at <= end_date)
|
||||||
|
if group_id:
|
||||||
|
group_users = db.query(GroupMember.user_id).filter(GroupMember.group_id == group_id).subquery()
|
||||||
|
query = query.filter(ChatMessage.user_id.in_(group_users))
|
||||||
|
|
||||||
results = query.group_by(ChatMessage.model_id).all()
|
results = query.group_by(ChatMessage.model_id).all()
|
||||||
|
|
||||||
@@ -455,10 +465,12 @@ class ChatMessageTable:
|
|||||||
self,
|
self,
|
||||||
start_date: Optional[int] = None,
|
start_date: Optional[int] = None,
|
||||||
end_date: Optional[int] = None,
|
end_date: Optional[int] = None,
|
||||||
|
group_id: Optional[str] = None,
|
||||||
db: Optional[Session] = None,
|
db: Optional[Session] = None,
|
||||||
) -> dict[str, int]:
|
) -> dict[str, int]:
|
||||||
with get_db_context(db) as db:
|
with get_db_context(db) as db:
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
from open_webui.models.groups import GroupMember
|
||||||
|
|
||||||
query = db.query(
|
query = db.query(
|
||||||
ChatMessage.user_id, func.count(ChatMessage.id).label("count")
|
ChatMessage.user_id, func.count(ChatMessage.id).label("count")
|
||||||
@@ -468,6 +480,9 @@ class ChatMessageTable:
|
|||||||
query = query.filter(ChatMessage.created_at >= start_date)
|
query = query.filter(ChatMessage.created_at >= start_date)
|
||||||
if end_date:
|
if end_date:
|
||||||
query = query.filter(ChatMessage.created_at <= end_date)
|
query = query.filter(ChatMessage.created_at <= end_date)
|
||||||
|
if group_id:
|
||||||
|
group_users = db.query(GroupMember.user_id).filter(GroupMember.group_id == group_id).subquery()
|
||||||
|
query = query.filter(ChatMessage.user_id.in_(group_users))
|
||||||
|
|
||||||
results = query.group_by(ChatMessage.user_id).all()
|
results = query.group_by(ChatMessage.user_id).all()
|
||||||
return {row.user_id: row.count for row in results}
|
return {row.user_id: row.count for row in results}
|
||||||
@@ -476,10 +491,12 @@ class ChatMessageTable:
|
|||||||
self,
|
self,
|
||||||
start_date: Optional[int] = None,
|
start_date: Optional[int] = None,
|
||||||
end_date: Optional[int] = None,
|
end_date: Optional[int] = None,
|
||||||
|
group_id: Optional[str] = None,
|
||||||
db: Optional[Session] = None,
|
db: Optional[Session] = None,
|
||||||
) -> dict[str, int]:
|
) -> dict[str, int]:
|
||||||
with get_db_context(db) as db:
|
with get_db_context(db) as db:
|
||||||
from sqlalchemy import func
|
from sqlalchemy import func
|
||||||
|
from open_webui.models.groups import GroupMember
|
||||||
|
|
||||||
query = db.query(
|
query = db.query(
|
||||||
ChatMessage.chat_id, func.count(ChatMessage.id).label("count")
|
ChatMessage.chat_id, func.count(ChatMessage.id).label("count")
|
||||||
@@ -489,6 +506,9 @@ class ChatMessageTable:
|
|||||||
query = query.filter(ChatMessage.created_at >= start_date)
|
query = query.filter(ChatMessage.created_at >= start_date)
|
||||||
if end_date:
|
if end_date:
|
||||||
query = query.filter(ChatMessage.created_at <= end_date)
|
query = query.filter(ChatMessage.created_at <= end_date)
|
||||||
|
if group_id:
|
||||||
|
group_users = db.query(GroupMember.user_id).filter(GroupMember.group_id == group_id).subquery()
|
||||||
|
query = query.filter(ChatMessage.user_id.in_(group_users))
|
||||||
|
|
||||||
results = query.group_by(ChatMessage.chat_id).all()
|
results = query.group_by(ChatMessage.chat_id).all()
|
||||||
return {row.chat_id: row.count for row in results}
|
return {row.chat_id: row.count for row in results}
|
||||||
@@ -497,11 +517,13 @@ class ChatMessageTable:
|
|||||||
self,
|
self,
|
||||||
start_date: Optional[int] = None,
|
start_date: Optional[int] = None,
|
||||||
end_date: Optional[int] = None,
|
end_date: Optional[int] = None,
|
||||||
|
group_id: Optional[str] = None,
|
||||||
db: Optional[Session] = None,
|
db: Optional[Session] = None,
|
||||||
) -> dict[str, dict[str, int]]:
|
) -> dict[str, dict[str, int]]:
|
||||||
"""Get message counts grouped by day and model."""
|
"""Get message counts grouped by day and model."""
|
||||||
with get_db_context(db) as db:
|
with get_db_context(db) as db:
|
||||||
from datetime import datetime, timedelta
|
from datetime import datetime, timedelta
|
||||||
|
from open_webui.models.groups import GroupMember
|
||||||
|
|
||||||
query = db.query(ChatMessage.created_at, ChatMessage.model_id).filter(
|
query = db.query(ChatMessage.created_at, ChatMessage.model_id).filter(
|
||||||
ChatMessage.role == "assistant",
|
ChatMessage.role == "assistant",
|
||||||
@@ -513,6 +535,9 @@ class ChatMessageTable:
|
|||||||
query = query.filter(ChatMessage.created_at >= start_date)
|
query = query.filter(ChatMessage.created_at >= start_date)
|
||||||
if end_date:
|
if end_date:
|
||||||
query = query.filter(ChatMessage.created_at <= end_date)
|
query = query.filter(ChatMessage.created_at <= end_date)
|
||||||
|
if group_id:
|
||||||
|
group_users = db.query(GroupMember.user_id).filter(GroupMember.group_id == group_id).subquery()
|
||||||
|
query = query.filter(ChatMessage.user_id.in_(group_users))
|
||||||
|
|
||||||
results = query.all()
|
results = query.all()
|
||||||
|
|
||||||
|
|||||||
@@ -7,6 +7,7 @@ from pydantic import BaseModel
|
|||||||
|
|
||||||
from open_webui.models.chat_messages import ChatMessages, ChatMessageModel
|
from open_webui.models.chat_messages import ChatMessages, ChatMessageModel
|
||||||
from open_webui.models.chats import Chats
|
from open_webui.models.chats import Chats
|
||||||
|
from open_webui.models.groups import Groups
|
||||||
from open_webui.models.users import Users
|
from open_webui.models.users import Users
|
||||||
from open_webui.models.feedbacks import Feedbacks
|
from open_webui.models.feedbacks import Feedbacks
|
||||||
from open_webui.utils.auth import get_admin_user
|
from open_webui.utils.auth import get_admin_user
|
||||||
@@ -56,12 +57,13 @@ class UserAnalyticsResponse(BaseModel):
|
|||||||
async def get_model_analytics(
|
async def get_model_analytics(
|
||||||
start_date: Optional[int] = Query(None, description="Start timestamp (epoch)"),
|
start_date: Optional[int] = Query(None, description="Start timestamp (epoch)"),
|
||||||
end_date: Optional[int] = Query(None, description="End 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),
|
user=Depends(get_admin_user),
|
||||||
db: Session = Depends(get_session),
|
db: Session = Depends(get_session),
|
||||||
):
|
):
|
||||||
"""Get message counts per model."""
|
"""Get message counts per model."""
|
||||||
counts = ChatMessages.get_message_count_by_model(
|
counts = ChatMessages.get_message_count_by_model(
|
||||||
start_date=start_date, end_date=end_date, db=db
|
start_date=start_date, end_date=end_date, group_id=group_id, db=db
|
||||||
)
|
)
|
||||||
models = [
|
models = [
|
||||||
ModelAnalyticsEntry(model_id=model_id, count=count)
|
ModelAnalyticsEntry(model_id=model_id, count=count)
|
||||||
@@ -74,15 +76,14 @@ async def get_model_analytics(
|
|||||||
async def get_user_analytics(
|
async def get_user_analytics(
|
||||||
start_date: Optional[int] = Query(None, description="Start timestamp (epoch)"),
|
start_date: Optional[int] = Query(None, description="Start timestamp (epoch)"),
|
||||||
end_date: Optional[int] = Query(None, description="End 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"),
|
limit: int = Query(50, description="Max users to return"),
|
||||||
user=Depends(get_admin_user),
|
user=Depends(get_admin_user),
|
||||||
db: Session = Depends(get_session),
|
db: Session = Depends(get_session),
|
||||||
):
|
):
|
||||||
"""Get message counts and token usage per user with user info."""
|
"""Get message counts and token usage per user with user info."""
|
||||||
from open_webui.models.users import Users
|
|
||||||
|
|
||||||
counts = ChatMessages.get_message_count_by_user(
|
counts = ChatMessages.get_message_count_by_user(
|
||||||
start_date=start_date, end_date=end_date, db=db
|
start_date=start_date, end_date=end_date, group_id=group_id, db=db
|
||||||
)
|
)
|
||||||
token_usage = ChatMessages.get_token_usage_by_user(
|
token_usage = ChatMessages.get_token_usage_by_user(
|
||||||
start_date=start_date, end_date=end_date, db=db
|
start_date=start_date, end_date=end_date, db=db
|
||||||
@@ -153,18 +154,19 @@ class SummaryResponse(BaseModel):
|
|||||||
async def get_summary(
|
async def get_summary(
|
||||||
start_date: Optional[int] = Query(None, description="Start timestamp (epoch)"),
|
start_date: Optional[int] = Query(None, description="Start timestamp (epoch)"),
|
||||||
end_date: Optional[int] = Query(None, description="End 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),
|
user=Depends(get_admin_user),
|
||||||
db: Session = Depends(get_session),
|
db: Session = Depends(get_session),
|
||||||
):
|
):
|
||||||
"""Get summary statistics for the dashboard."""
|
"""Get summary statistics for the dashboard."""
|
||||||
model_counts = ChatMessages.get_message_count_by_model(
|
model_counts = ChatMessages.get_message_count_by_model(
|
||||||
start_date=start_date, end_date=end_date, db=db
|
start_date=start_date, end_date=end_date, group_id=group_id, db=db
|
||||||
)
|
)
|
||||||
user_counts = ChatMessages.get_message_count_by_user(
|
user_counts = ChatMessages.get_message_count_by_user(
|
||||||
start_date=start_date, end_date=end_date, db=db
|
start_date=start_date, end_date=end_date, group_id=group_id, db=db
|
||||||
)
|
)
|
||||||
chat_counts = ChatMessages.get_message_count_by_chat(
|
chat_counts = ChatMessages.get_message_count_by_chat(
|
||||||
start_date=start_date, end_date=end_date, db=db
|
start_date=start_date, end_date=end_date, group_id=group_id, db=db
|
||||||
)
|
)
|
||||||
|
|
||||||
return SummaryResponse(
|
return SummaryResponse(
|
||||||
@@ -188,6 +190,7 @@ class DailyStatsResponse(BaseModel):
|
|||||||
async def get_daily_stats(
|
async def get_daily_stats(
|
||||||
start_date: Optional[int] = Query(None, description="Start timestamp (epoch)"),
|
start_date: Optional[int] = Query(None, description="Start timestamp (epoch)"),
|
||||||
end_date: Optional[int] = Query(None, description="End 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'"),
|
granularity: str = Query("daily", description="Granularity: 'hourly' or 'daily'"),
|
||||||
user=Depends(get_admin_user),
|
user=Depends(get_admin_user),
|
||||||
db: Session = Depends(get_session),
|
db: Session = Depends(get_session),
|
||||||
@@ -199,7 +202,7 @@ async def get_daily_stats(
|
|||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
counts = ChatMessages.get_daily_message_counts_by_model(
|
counts = ChatMessages.get_daily_message_counts_by_model(
|
||||||
start_date=start_date, end_date=end_date, db=db
|
start_date=start_date, end_date=end_date, group_id=group_id, db=db
|
||||||
)
|
)
|
||||||
return DailyStatsResponse(
|
return DailyStatsResponse(
|
||||||
data=[
|
data=[
|
||||||
@@ -228,12 +231,13 @@ class TokenUsageResponse(BaseModel):
|
|||||||
async def get_token_usage(
|
async def get_token_usage(
|
||||||
start_date: Optional[int] = Query(None),
|
start_date: Optional[int] = Query(None),
|
||||||
end_date: Optional[int] = Query(None),
|
end_date: Optional[int] = Query(None),
|
||||||
|
group_id: Optional[str] = Query(None, description="Filter by user group ID"),
|
||||||
user=Depends(get_admin_user),
|
user=Depends(get_admin_user),
|
||||||
db: Session = Depends(get_session),
|
db: Session = Depends(get_session),
|
||||||
):
|
):
|
||||||
"""Get token usage aggregated by model."""
|
"""Get token usage aggregated by model."""
|
||||||
usage = ChatMessages.get_token_usage_by_model(
|
usage = ChatMessages.get_token_usage_by_model(
|
||||||
start_date=start_date, end_date=end_date, db=db
|
start_date=start_date, end_date=end_date, group_id=group_id, db=db
|
||||||
)
|
)
|
||||||
|
|
||||||
models = [
|
models = [
|
||||||
|
|||||||
@@ -3,13 +3,15 @@ import { WEBUI_API_BASE_URL } from '$lib/constants';
|
|||||||
export const getModelAnalytics = async (
|
export const getModelAnalytics = async (
|
||||||
token: string = '',
|
token: string = '',
|
||||||
startDate: number | null = null,
|
startDate: number | null = null,
|
||||||
endDate: number | null = null
|
endDate: number | null = null,
|
||||||
|
groupId: string | null = null
|
||||||
) => {
|
) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
if (startDate) searchParams.append('start_date', startDate.toString());
|
if (startDate) searchParams.append('start_date', startDate.toString());
|
||||||
if (endDate) searchParams.append('end_date', endDate.toString());
|
if (endDate) searchParams.append('end_date', endDate.toString());
|
||||||
|
if (groupId) searchParams.append('group_id', groupId);
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/models?${searchParams.toString()}`, {
|
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/models?${searchParams.toString()}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -40,7 +42,8 @@ export const getUserAnalytics = async (
|
|||||||
token: string = '',
|
token: string = '',
|
||||||
startDate: number | null = null,
|
startDate: number | null = null,
|
||||||
endDate: number | null = null,
|
endDate: number | null = null,
|
||||||
limit: number = 50
|
limit: number = 50,
|
||||||
|
groupId: string | null = null
|
||||||
) => {
|
) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
@@ -48,6 +51,7 @@ export const getUserAnalytics = async (
|
|||||||
if (startDate) searchParams.append('start_date', startDate.toString());
|
if (startDate) searchParams.append('start_date', startDate.toString());
|
||||||
if (endDate) searchParams.append('end_date', endDate.toString());
|
if (endDate) searchParams.append('end_date', endDate.toString());
|
||||||
if (limit) searchParams.append('limit', limit.toString());
|
if (limit) searchParams.append('limit', limit.toString());
|
||||||
|
if (groupId) searchParams.append('group_id', groupId);
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/users?${searchParams.toString()}`, {
|
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/users?${searchParams.toString()}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -123,13 +127,15 @@ export const getMessages = async (
|
|||||||
export const getSummary = async (
|
export const getSummary = async (
|
||||||
token: string = '',
|
token: string = '',
|
||||||
startDate: number | null = null,
|
startDate: number | null = null,
|
||||||
endDate: number | null = null
|
endDate: number | null = null,
|
||||||
|
groupId: string | null = null
|
||||||
) => {
|
) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
if (startDate) searchParams.append('start_date', startDate.toString());
|
if (startDate) searchParams.append('start_date', startDate.toString());
|
||||||
if (endDate) searchParams.append('end_date', endDate.toString());
|
if (endDate) searchParams.append('end_date', endDate.toString());
|
||||||
|
if (groupId) searchParams.append('group_id', groupId);
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/summary?${searchParams.toString()}`, {
|
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/summary?${searchParams.toString()}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -160,7 +166,8 @@ export const getDailyStats = async (
|
|||||||
token: string = '',
|
token: string = '',
|
||||||
startDate: number | null = null,
|
startDate: number | null = null,
|
||||||
endDate: number | null = null,
|
endDate: number | null = null,
|
||||||
granularity: 'hourly' | 'daily' = 'daily'
|
granularity: 'hourly' | 'daily' = 'daily',
|
||||||
|
groupId: string | null = null
|
||||||
) => {
|
) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
@@ -168,6 +175,7 @@ export const getDailyStats = async (
|
|||||||
if (startDate) searchParams.append('start_date', startDate.toString());
|
if (startDate) searchParams.append('start_date', startDate.toString());
|
||||||
if (endDate) searchParams.append('end_date', endDate.toString());
|
if (endDate) searchParams.append('end_date', endDate.toString());
|
||||||
searchParams.append('granularity', granularity);
|
searchParams.append('granularity', granularity);
|
||||||
|
if (groupId) searchParams.append('group_id', groupId);
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/daily?${searchParams.toString()}`, {
|
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/daily?${searchParams.toString()}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
@@ -197,13 +205,15 @@ export const getDailyStats = async (
|
|||||||
export const getTokenUsage = async (
|
export const getTokenUsage = async (
|
||||||
token: string = '',
|
token: string = '',
|
||||||
startDate: number | null = null,
|
startDate: number | null = null,
|
||||||
endDate: number | null = null
|
endDate: number | null = null,
|
||||||
|
groupId: string | null = null
|
||||||
) => {
|
) => {
|
||||||
let error = null;
|
let error = null;
|
||||||
|
|
||||||
const searchParams = new URLSearchParams();
|
const searchParams = new URLSearchParams();
|
||||||
if (startDate) searchParams.append('start_date', startDate.toString());
|
if (startDate) searchParams.append('start_date', startDate.toString());
|
||||||
if (endDate) searchParams.append('end_date', endDate.toString());
|
if (endDate) searchParams.append('end_date', endDate.toString());
|
||||||
|
if (groupId) searchParams.append('group_id', groupId);
|
||||||
|
|
||||||
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/tokens?${searchParams.toString()}`, {
|
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/tokens?${searchParams.toString()}`, {
|
||||||
method: 'GET',
|
method: 'GET',
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
import { onMount, getContext } from 'svelte';
|
import { onMount, getContext } from 'svelte';
|
||||||
import { models } from '$lib/stores';
|
import { models } from '$lib/stores';
|
||||||
import { getSummary, getModelAnalytics, getUserAnalytics, getDailyStats, getTokenUsage } from '$lib/apis/analytics';
|
import { getSummary, getModelAnalytics, getUserAnalytics, getDailyStats, getTokenUsage } from '$lib/apis/analytics';
|
||||||
|
import { getGroups } from '$lib/apis/groups';
|
||||||
import Spinner from '$lib/components/common/Spinner.svelte';
|
import Spinner from '$lib/components/common/Spinner.svelte';
|
||||||
import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
|
import ChevronUp from '$lib/components/icons/ChevronUp.svelte';
|
||||||
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
|
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
|
||||||
@@ -24,6 +25,10 @@
|
|||||||
{ value: 'all', label: 'All time' }
|
{ value: 'all', label: 'All time' }
|
||||||
];
|
];
|
||||||
|
|
||||||
|
// User group filter
|
||||||
|
let groups: Array<{ id: string; name: string }> = [];
|
||||||
|
let selectedGroupId: string | null = null;
|
||||||
|
|
||||||
const getDateRange = (period: string): { start: number | null; end: number | null } => {
|
const getDateRange = (period: string): { start: number | null; end: number | null } => {
|
||||||
const now = Math.floor(Date.now() / 1000);
|
const now = Math.floor(Date.now() / 1000);
|
||||||
const day = 86400;
|
const day = 86400;
|
||||||
@@ -80,11 +85,11 @@
|
|||||||
const { start, end } = getDateRange(selectedPeriod);
|
const { start, end } = getDateRange(selectedPeriod);
|
||||||
const granularity = selectedPeriod === '24h' ? 'hourly' : 'daily';
|
const granularity = selectedPeriod === '24h' ? 'hourly' : 'daily';
|
||||||
const [summaryRes, modelsRes, usersRes, dailyRes, tokensRes] = await Promise.all([
|
const [summaryRes, modelsRes, usersRes, dailyRes, tokensRes] = await Promise.all([
|
||||||
getSummary(localStorage.token, start, end),
|
getSummary(localStorage.token, start, end, selectedGroupId),
|
||||||
getModelAnalytics(localStorage.token, start, end),
|
getModelAnalytics(localStorage.token, start, end, selectedGroupId),
|
||||||
getUserAnalytics(localStorage.token, start, end, 50),
|
getUserAnalytics(localStorage.token, start, end, 50, selectedGroupId),
|
||||||
getDailyStats(localStorage.token, start, end, granularity),
|
getDailyStats(localStorage.token, start, end, granularity, selectedGroupId),
|
||||||
getTokenUsage(localStorage.token, start, end)
|
getTokenUsage(localStorage.token, start, end, selectedGroupId)
|
||||||
]);
|
]);
|
||||||
|
|
||||||
summary = summaryRes ?? summary;
|
summary = summaryRes ?? summary;
|
||||||
@@ -120,10 +125,20 @@
|
|||||||
loading = false;
|
loading = false;
|
||||||
};
|
};
|
||||||
|
|
||||||
$: if (selectedPeriod) {
|
$: if (selectedPeriod || selectedGroupId !== undefined) {
|
||||||
loadDashboard();
|
loadDashboard();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
onMount(async () => {
|
||||||
|
// Load groups for filter
|
||||||
|
try {
|
||||||
|
const res = await getGroups(localStorage.token);
|
||||||
|
groups = res ?? [];
|
||||||
|
} catch (e) {
|
||||||
|
console.error('Failed to load groups:', e);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
$: sortedModels = [...modelStats].sort((a, b) => {
|
$: sortedModels = [...modelStats].sort((a, b) => {
|
||||||
if (modelOrderBy === 'name') {
|
if (modelOrderBy === 'name') {
|
||||||
return modelDirection === 'asc'
|
return modelDirection === 'asc'
|
||||||
@@ -159,14 +174,27 @@
|
|||||||
<div class="text-lg font-medium px-0.5">
|
<div class="text-lg font-medium px-0.5">
|
||||||
{$i18n.t('Analytics')}
|
{$i18n.t('Analytics')}
|
||||||
</div>
|
</div>
|
||||||
<select
|
<div class="flex items-center gap-2">
|
||||||
bind:value={selectedPeriod}
|
{#if groups.length > 0}
|
||||||
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-none text-right"
|
<select
|
||||||
>
|
bind:value={selectedGroupId}
|
||||||
{#each periods as period}
|
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-none text-right"
|
||||||
<option value={period.value}>{$i18n.t(period.label)}</option>
|
>
|
||||||
{/each}
|
<option value={null}>{$i18n.t('All Users')}</option>
|
||||||
</select>
|
{#each groups as group}
|
||||||
|
<option value={group.id}>{group.name}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
{/if}
|
||||||
|
<select
|
||||||
|
bind:value={selectedPeriod}
|
||||||
|
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-none text-right"
|
||||||
|
>
|
||||||
|
{#each periods as period}
|
||||||
|
<option value={period.value}>{$i18n.t(period.label)}</option>
|
||||||
|
{/each}
|
||||||
|
</select>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- Model Details Modal -->
|
<!-- Model Details Modal -->
|
||||||
|
|||||||
Reference in New Issue
Block a user