Adds a "Your stats" section to the Profile settings tab, built entirely from local history in studio.db. No telemetry, nothing uploaded. Backend - storage/profile_stats_db.py folds every metric in one streaming pass over chat_messages, memoised against a (count, max created_at) fingerprint so reopening the tab is free until history changes. - routes/profile_stats.py serves GET /api/profile/stats from a worker thread, so a cold pass cannot stall token streaming. Frontend - Headline tiles, a token activity grid with daily/weekly/cumulative modes, activity insights, most used models, hour and weekday rhythms, and training run totals. - The stats panel is lazy loaded so recharts stays out of the main bundle. - Personalization panel now puts a larger avatar beside the two name fields, with picture options moved into an edit popover. - "Sloth in greeting" moves to Chat defaults, next to the other chat toggles. Tests: 9 backend tests including a regression test that the endpoint does not block the event loop, plus formatting unit tests.
23 lines
840 B
TypeScript
23 lines
840 B
TypeScript
// SPDX-License-Identifier: AGPL-3.0-only
|
|
// Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
|
|
|
import { Suspense, lazy } from "react";
|
|
import { StatsSkeleton } from "./stats-skeleton";
|
|
|
|
// The stats content pulls in recharts for the rhythm charts. Settings live in
|
|
// the main bundle, so importing it eagerly would move ~300 KB of charting off
|
|
// its own lazy chunk and onto every cold app load. Split it here instead: the
|
|
// chunk is fetched only when someone actually opens Settings -> Profile.
|
|
const ProfileStatsContent = lazy(() =>
|
|
import("./profile-stats-content").then((module) => ({
|
|
default: module.ProfileStatsContent,
|
|
})),
|
|
);
|
|
|
|
export function ProfileStatsPanel() {
|
|
return (
|
|
<Suspense fallback={<StatsSkeleton />}>
|
|
<ProfileStatsContent />
|
|
</Suspense>
|
|
);
|
|
}
|