unsloth/studio/frontend/src/features/profile/components/stats/profile-stats-panel.tsx
Unsloth b8364e3445 Studio: add profile usage stats and tidy the personalization panel
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.
2026-07-28 22:54:28 -07:00

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>
);
}