enh: analytics
This commit is contained in:
@@ -3,13 +3,15 @@ import { WEBUI_API_BASE_URL } from '$lib/constants';
|
||||
export const getModelAnalytics = async (
|
||||
token: string = '',
|
||||
startDate: number | null = null,
|
||||
endDate: number | null = null
|
||||
endDate: number | null = null,
|
||||
groupId: string | null = null
|
||||
) => {
|
||||
let error = null;
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
if (startDate) searchParams.append('start_date', startDate.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()}`, {
|
||||
method: 'GET',
|
||||
@@ -40,7 +42,8 @@ export const getUserAnalytics = async (
|
||||
token: string = '',
|
||||
startDate: number | null = null,
|
||||
endDate: number | null = null,
|
||||
limit: number = 50
|
||||
limit: number = 50,
|
||||
groupId: string | null = null
|
||||
) => {
|
||||
let error = null;
|
||||
|
||||
@@ -48,6 +51,7 @@ export const getUserAnalytics = async (
|
||||
if (startDate) searchParams.append('start_date', startDate.toString());
|
||||
if (endDate) searchParams.append('end_date', endDate.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()}`, {
|
||||
method: 'GET',
|
||||
@@ -123,13 +127,15 @@ export const getMessages = async (
|
||||
export const getSummary = async (
|
||||
token: string = '',
|
||||
startDate: number | null = null,
|
||||
endDate: number | null = null
|
||||
endDate: number | null = null,
|
||||
groupId: string | null = null
|
||||
) => {
|
||||
let error = null;
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
if (startDate) searchParams.append('start_date', startDate.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()}`, {
|
||||
method: 'GET',
|
||||
@@ -160,7 +166,8 @@ export const getDailyStats = async (
|
||||
token: string = '',
|
||||
startDate: number | null = null,
|
||||
endDate: number | null = null,
|
||||
granularity: 'hourly' | 'daily' = 'daily'
|
||||
granularity: 'hourly' | 'daily' = 'daily',
|
||||
groupId: string | null = null
|
||||
) => {
|
||||
let error = null;
|
||||
|
||||
@@ -168,6 +175,7 @@ export const getDailyStats = async (
|
||||
if (startDate) searchParams.append('start_date', startDate.toString());
|
||||
if (endDate) searchParams.append('end_date', endDate.toString());
|
||||
searchParams.append('granularity', granularity);
|
||||
if (groupId) searchParams.append('group_id', groupId);
|
||||
|
||||
const res = await fetch(`${WEBUI_API_BASE_URL}/analytics/daily?${searchParams.toString()}`, {
|
||||
method: 'GET',
|
||||
@@ -197,13 +205,15 @@ export const getDailyStats = async (
|
||||
export const getTokenUsage = async (
|
||||
token: string = '',
|
||||
startDate: number | null = null,
|
||||
endDate: number | null = null
|
||||
endDate: number | null = null,
|
||||
groupId: string | null = null
|
||||
) => {
|
||||
let error = null;
|
||||
|
||||
const searchParams = new URLSearchParams();
|
||||
if (startDate) searchParams.append('start_date', startDate.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()}`, {
|
||||
method: 'GET',
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
import { onMount, getContext } from 'svelte';
|
||||
import { models } from '$lib/stores';
|
||||
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 ChevronUp from '$lib/components/icons/ChevronUp.svelte';
|
||||
import ChevronDown from '$lib/components/icons/ChevronDown.svelte';
|
||||
@@ -24,6 +25,10 @@
|
||||
{ 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 now = Math.floor(Date.now() / 1000);
|
||||
const day = 86400;
|
||||
@@ -80,11 +85,11 @@
|
||||
const { start, end } = getDateRange(selectedPeriod);
|
||||
const granularity = selectedPeriod === '24h' ? 'hourly' : 'daily';
|
||||
const [summaryRes, modelsRes, usersRes, dailyRes, tokensRes] = await Promise.all([
|
||||
getSummary(localStorage.token, start, end),
|
||||
getModelAnalytics(localStorage.token, start, end),
|
||||
getUserAnalytics(localStorage.token, start, end, 50),
|
||||
getDailyStats(localStorage.token, start, end, granularity),
|
||||
getTokenUsage(localStorage.token, start, end)
|
||||
getSummary(localStorage.token, start, end, selectedGroupId),
|
||||
getModelAnalytics(localStorage.token, start, end, selectedGroupId),
|
||||
getUserAnalytics(localStorage.token, start, end, 50, selectedGroupId),
|
||||
getDailyStats(localStorage.token, start, end, granularity, selectedGroupId),
|
||||
getTokenUsage(localStorage.token, start, end, selectedGroupId)
|
||||
]);
|
||||
|
||||
summary = summaryRes ?? summary;
|
||||
@@ -120,10 +125,20 @@
|
||||
loading = false;
|
||||
};
|
||||
|
||||
$: if (selectedPeriod) {
|
||||
$: if (selectedPeriod || selectedGroupId !== undefined) {
|
||||
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) => {
|
||||
if (modelOrderBy === 'name') {
|
||||
return modelDirection === 'asc'
|
||||
@@ -159,14 +174,27 @@
|
||||
<div class="text-lg font-medium px-0.5">
|
||||
{$i18n.t('Analytics')}
|
||||
</div>
|
||||
<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 class="flex items-center gap-2">
|
||||
{#if groups.length > 0}
|
||||
<select
|
||||
bind:value={selectedGroupId}
|
||||
class="dark:bg-gray-900 w-fit pr-8 rounded-sm px-2 text-xs bg-transparent outline-none text-right"
|
||||
>
|
||||
<option value={null}>{$i18n.t('All Users')}</option>
|
||||
{#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>
|
||||
|
||||
<!-- Model Details Modal -->
|
||||
|
||||
Reference in New Issue
Block a user