From d61aa853f18ed9b0115827b3f360248e0c517f48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=90=89=E9=91=AB=E5=8D=87?= Date: Wed, 10 Jun 2026 11:24:21 +0800 Subject: [PATCH] =?UTF-8?q?feat(admin):=20=E7=94=A8=E6=88=B7=E6=8E=92?= =?UTF-8?q?=E8=A1=8C=E6=A6=9C=E6=AF=8F=E6=97=A5=E5=BF=AB=E7=85=A7=E2=80=94?= =?UTF-8?q?=E2=80=94=E6=89=8B=E5=8A=A8=E5=88=B7=E6=96=B0=E5=BD=93=E5=A4=A9?= =?UTF-8?q?=20+=20=E5=87=8C=E6=99=A8=E8=87=AA=E5=8A=A8=E5=AE=9A=E7=A8=BF?= =?UTF-8?q?=E5=89=8D=E4=B8=80=E5=A4=A9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 新增 POST /api/admin/users/leaderboard/refresh-today:同步强制重建当天快照 并返回合并区间;前端 useRefreshTodayLeaderboard + AdminUserLeaderboardPage 「刷新当天」按钮 - Gateway 调度循环在每日 ADMIN_LEADERBOARD_FINALIZE_HOUR(默认凌晨1点)对前一天 强制重算一次,避免临近午夜落库的 runs/tool-calls 漏统计;判定逻辑下沉为纯函数 deerflow.persistence.admin_stats.finalize.should_finalize_previous_day - 新增 scripts/sql/column_additions_mysql.sql:既有 MySQL 库手动补列的幂等脚本 (Alembic 自动迁移已禁用,create_all 不改既有表) - CLAUDE.md 补充排行榜快照、Schema 迁移约定,并补齐技能/知识库选模型的文档 Co-Authored-By: Claude Opus 4.8 (1M context) --- frontend-web/src/core/admin/api.ts | 26 +++++++ frontend-web/src/core/admin/hooks.ts | 31 ++++++++ .../src/pages/AdminUserLeaderboardPage.tsx | 28 ++++++- offline-backend-20260512/backend/CLAUDE.md | 22 +++++- .../backend/app/gateway/app.py | 65 ++++++++++++++++ .../app/gateway/routers/admin_users.py | 66 +++++++++++++++++ .../persistence/admin_stats/finalize.py | 47 ++++++++++++ .../scripts/sql/column_additions_mysql.sql | 74 +++++++++++++++++++ 8 files changed, 356 insertions(+), 3 deletions(-) create mode 100644 offline-backend-20260512/backend/packages/harness/deerflow/persistence/admin_stats/finalize.py create mode 100644 offline-backend-20260512/backend/scripts/sql/column_additions_mysql.sql diff --git a/frontend-web/src/core/admin/api.ts b/frontend-web/src/core/admin/api.ts index 70d99b4a..62f1be45 100644 --- a/frontend-web/src/core/admin/api.ts +++ b/frontend-web/src/core/admin/api.ts @@ -247,6 +247,32 @@ export async function getUserLeaderboard( ); } +/** + * Force a synchronous rebuild of *today's* leaderboard snapshot so the latest + * real-time records are included, then return the leaderboard for `range`. + */ +export async function refreshTodayLeaderboard( + range: { + since: string; + until: string; + days?: number; + }, + limit = 20, +): Promise { + const params = new URLSearchParams({ + days: String(range.days ?? 30), + since: range.since, + until: range.until, + limit: String(limit), + }); + return parse( + await apiFetch(`${base()}/leaderboard/refresh-today?${params}`, { + method: "POST", + }), + "Failed to refresh today's leaderboard", + ); +} + export async function getUserAnalytics( userId: string, range: { since: string; until: string; days?: number }, diff --git a/frontend-web/src/core/admin/hooks.ts b/frontend-web/src/core/admin/hooks.ts index cf75c366..228dad1c 100644 --- a/frontend-web/src/core/admin/hooks.ts +++ b/frontend-web/src/core/admin/hooks.ts @@ -9,6 +9,7 @@ import { getUserMemory, getUserAnalytics, getUserLeaderboard, + refreshTodayLeaderboard, listUserQa, listUserThreadMessages, listUserThreads, @@ -41,6 +42,36 @@ export function useUserLeaderboard( }); } +/** + * Force-rebuild today's snapshot, then prime + invalidate the matching + * leaderboard query so the page shows the freshly recomputed numbers. + */ +export function useRefreshTodayLeaderboard() { + const qc = useQueryClient(); + return useMutation({ + mutationFn: ({ + range, + limit = 20, + }: { + range: { since: string; until: string; days?: number }; + limit?: number; + }) => refreshTodayLeaderboard(range, limit), + onSuccess: (data, variables) => { + qc.setQueryData( + [ + "admin-activity", + "leaderboard", + variables.range.since, + variables.range.until, + variables.limit ?? 20, + ], + data, + ); + qc.invalidateQueries({ queryKey: ["admin-activity", "leaderboard"] }); + }, + }); +} + export function useUserAnalytics( userId: string | null, range: { since: string; until: string; days?: number }, diff --git a/frontend-web/src/pages/AdminUserLeaderboardPage.tsx b/frontend-web/src/pages/AdminUserLeaderboardPage.tsx index 62652e7f..de98095f 100644 --- a/frontend-web/src/pages/AdminUserLeaderboardPage.tsx +++ b/frontend-web/src/pages/AdminUserLeaderboardPage.tsx @@ -18,6 +18,7 @@ import { Medal, MessageSquareText, MousePointerClick, + RefreshCw, Search, Settings2, Sparkles, @@ -55,7 +56,7 @@ import type { AdminLeaderboardUser, } from "@/core/admin/api"; import { streamUserLeaderboardReport } from "@/core/admin/api"; -import { useUserAnalytics, useUserLeaderboard } from "@/core/admin/hooks"; +import { useRefreshTodayLeaderboard, useUserAnalytics, useUserLeaderboard } from "@/core/admin/hooks"; import { useModels } from "@/core/models/hooks"; import { downloadMarkdownAsDocx } from "@/core/scheduled-tasks/markdown-docx"; import { cn } from "@/lib/utils"; @@ -1052,6 +1053,7 @@ export default function AdminUserLeaderboardPage() { [range], ); const { data, isLoading, error } = useUserLeaderboard(apiRange, 100); + const refreshToday = useRefreshTodayLeaderboard(); const [reportOpen, setReportOpen] = useState(false); const { models: reportModels, isLoading: reportModelsLoading } = useModels({ enabled: reportOpen }); const [reportModelName, setReportModelName] = useState(DEFAULT_REPORT_MODEL); @@ -1269,6 +1271,30 @@ export default function AdminUserLeaderboardPage() { > +