From 12ce92c6d92fa05f0bb0751ee16db3bc0436aee8 Mon Sep 17 00:00:00 2001 From: "mula.liu" Date: Thu, 2 Apr 2026 12:40:17 +0800 Subject: [PATCH] v0.1.4-p4 --- backend/core/database.py | 38 ++++++++++++++ backend/services/platform_activity_service.py | 36 ++++++++----- .../platform/PlatformDashboardPage.css | 52 ++++++++++--------- .../PlatformBotActivityAnalyticsSection.tsx | 4 +- 4 files changed, 90 insertions(+), 40 deletions(-) diff --git a/backend/core/database.py b/backend/core/database.py index 089be49..bb978bd 100644 --- a/backend/core/database.py +++ b/backend/core/database.py @@ -174,6 +174,43 @@ def _ensure_topic_columns() -> None: conn.commit() +def _ensure_platform_indexes() -> None: + inspector = inspect(engine) + with engine.connect() as conn: + if inspector.has_table(BOT_ACTIVITY_EVENT_TABLE): + try: + conn.execute( + text( + f""" + CREATE INDEX IF NOT EXISTS idx_bot_activity_event_bot_id_request_present + ON {BOT_ACTIVITY_EVENT_TABLE} (bot_id) + WHERE request_id IS NOT NULL AND request_id <> '' + """ + ) + ) + except Exception: + # Fall back silently when the current database dialect does not support partial indexes. + conn.execute( + text( + f""" + CREATE INDEX IF NOT EXISTS idx_bot_activity_event_bot_id + ON {BOT_ACTIVITY_EVENT_TABLE} (bot_id) + """ + ) + ) + + if inspector.has_table(BOT_REQUEST_USAGE_TABLE): + conn.execute( + text( + f""" + CREATE INDEX IF NOT EXISTS idx_bot_request_usage_started_at_bot_id + ON {BOT_REQUEST_USAGE_TABLE} (started_at, bot_id) + """ + ) + ) + conn.commit() + + def align_postgres_sequences() -> None: if engine.dialect.name != "postgresql": return @@ -215,6 +252,7 @@ def init_database() -> None: _ensure_bot_request_usage_columns() _ensure_botinstance_columns() _ensure_topic_columns() + _ensure_platform_indexes() align_postgres_sequences() finally: _release_migration_lock(lock_conn) diff --git a/backend/services/platform_activity_service.py b/backend/services/platform_activity_service.py index a607ff3..06696ef 100644 --- a/backend/services/platform_activity_service.py +++ b/backend/services/platform_activity_service.py @@ -104,24 +104,34 @@ def list_activity_events( def get_bot_activity_stats(session: Session) -> List[Dict[str, Any]]: - from sqlalchemy import and_, func + from sqlalchemy import func from models.bot import BotInstance + from models.platform import BotRequestUsage + + today = _utcnow().date() + first_day = today - timedelta(days=6) + first_started_at = datetime.combine(first_day, datetime.min.time()) + + activity_counts = ( + select( + BotRequestUsage.bot_id.label("bot_id"), + func.count(BotRequestUsage.id).label("count"), + ) + .where(BotRequestUsage.started_at >= first_started_at) + .group_by(BotRequestUsage.bot_id) + .subquery() + ) stmt = ( - select(BotInstance.id, BotInstance.name, func.count(BotActivityEvent.id).label("count")) - .select_from(BotInstance) - .join( - BotActivityEvent, - and_( - BotActivityEvent.bot_id == BotInstance.id, - BotActivityEvent.request_id.is_not(None), - func.length(func.trim(BotActivityEvent.request_id)) > 0, - ), - isouter=True, + select( + BotInstance.id, + BotInstance.name, + func.coalesce(activity_counts.c.count, 0).label("count"), ) + .select_from(BotInstance) + .join(activity_counts, activity_counts.c.bot_id == BotInstance.id, isouter=True) .where(BotInstance.enabled.is_(True)) - .group_by(BotInstance.id, BotInstance.name) - .order_by(func.count(BotActivityEvent.id).desc(), BotInstance.name.asc(), BotInstance.id.asc()) + .order_by(func.coalesce(activity_counts.c.count, 0).desc(), BotInstance.name.asc(), BotInstance.id.asc()) ) results = session.exec(stmt).all() diff --git a/frontend/src/modules/platform/PlatformDashboardPage.css b/frontend/src/modules/platform/PlatformDashboardPage.css index 1d11522..d2ccb0c 100644 --- a/frontend/src/modules/platform/PlatformDashboardPage.css +++ b/frontend/src/modules/platform/PlatformDashboardPage.css @@ -46,7 +46,7 @@ .platform-bot-list-scroll { display: flex; flex-direction: column; - gap: 10px; + gap: 8px; min-height: 0; overflow: auto; padding-right: 2px; @@ -57,8 +57,8 @@ position: relative; display: flex; flex-direction: column; - gap: 10px; - padding: 14px 14px 14px 18px; + gap: 6px; + padding: 10px 12px 10px 16px; border-radius: 16px; border: 1px solid var(--line); background: linear-gradient(145deg, color-mix(in oklab, var(--panel-soft) 86%, var(--panel) 14%), color-mix(in oklab, var(--panel-soft) 94%, transparent 6%)); @@ -147,11 +147,12 @@ .platform-bot-name-row { display: inline-flex; align-items: center; - gap: 6px; + gap: 5px; } .platform-bot-name { - font-size: 16px; + font-size: 15px; + line-height: 1.2; font-weight: 800; color: var(--title); min-width: 0; @@ -162,9 +163,10 @@ .platform-bot-id, .platform-bot-meta { - margin-top: 2px; + margin-top: 0; color: var(--subtitle); - font-size: 12px; + font-size: 11px; + line-height: 1.25; font-weight: 600; } @@ -184,17 +186,17 @@ } .platform-bot-actions { - margin-top: 10px; + margin-top: 4px; display: flex; align-items: center; justify-content: space-between; - gap: 6px; + gap: 4px; } .platform-bot-actions-main { display: inline-flex; align-items: center; - gap: 6px; + gap: 4px; } .platform-enable-switch { @@ -214,8 +216,8 @@ .platform-enable-switch-track { position: relative; - width: 36px; - height: 20px; + width: 32px; + height: 18px; border-radius: 999px; border: 1px solid color-mix(in oklab, var(--line) 82%, transparent); background: color-mix(in oklab, #9ca3b5 42%, var(--panel-soft) 58%); @@ -227,8 +229,8 @@ position: absolute; left: 2px; top: 2px; - width: 14px; - height: 14px; + width: 12px; + height: 12px; border-radius: 999px; background: color-mix(in oklab, var(--text) 75%, #fff 25%); transition: transform 0.2s ease, background 0.2s ease; @@ -240,7 +242,7 @@ } .platform-enable-switch input:checked + .platform-enable-switch-track::after { - transform: translateX(16px); + transform: translateX(14px); background: #fff; } @@ -250,18 +252,18 @@ } .platform-bot-icon-btn { - width: 36px; - height: 36px; + width: 30px; + height: 30px; padding: 0; - border-radius: 10px; + border-radius: 9px; display: inline-flex; align-items: center; justify-content: center; } .platform-bot-icon-btn svg { - width: 17px; - height: 17px; + width: 15px; + height: 15px; stroke-width: 2.1; } @@ -329,9 +331,9 @@ } .platform-bot-lock { - width: 16px; - height: 16px; - min-width: 16px; + width: 14px; + height: 14px; + min-width: 14px; display: inline-flex; align-items: center; justify-content: center; @@ -339,8 +341,8 @@ } .platform-bot-lock svg { - width: 12px; - height: 12px; + width: 11px; + height: 11px; stroke-width: 2.2; } diff --git a/frontend/src/modules/platform/components/PlatformBotActivityAnalyticsSection.tsx b/frontend/src/modules/platform/components/PlatformBotActivityAnalyticsSection.tsx index c6c34fa..efd3411 100644 --- a/frontend/src/modules/platform/components/PlatformBotActivityAnalyticsSection.tsx +++ b/frontend/src/modules/platform/components/PlatformBotActivityAnalyticsSection.tsx @@ -23,7 +23,7 @@ export function PlatformBotActivityAnalyticsSection({

{isZh ? 'Bot 活跃度分析' : 'Bot Activity Analytics'}

- {isZh ? '基于 bot_activity_event 中 request_id 非空记录的活跃度统计' : 'Activity statistics based on non-empty request_id rows in bot_activity_event'} + {isZh ? '最近 7 天 · 基于模型调用记录的 Bot 请求统计' : 'Last 7 days · Bot request statistics based on model usage records'}
@@ -37,7 +37,7 @@ export function PlatformBotActivityAnalyticsSection({ ) : null} {!loading && (!activityStats || activityStats.length === 0) ? ( -
{isZh ? '暂无活跃度数据。' : 'No activity data.'}
+
{isZh ? '最近 7 天暂无活跃度数据。' : 'No activity data in the last 7 days.'}
) : null} {activityStats && activityStats.length > 0 ? (() => {