v0.1.4-p4
parent
9699b4e7c9
commit
12ce92c6d9
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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()
|
||||
|
||||
|
|
|
|||
|
|
@ -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;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,7 +23,7 @@ export function PlatformBotActivityAnalyticsSection({
|
|||
<div>
|
||||
<h2>{isZh ? 'Bot 活跃度分析' : 'Bot Activity Analytics'}</h2>
|
||||
<div className="platform-model-analytics-subtitle">
|
||||
{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'}
|
||||
</div>
|
||||
</div>
|
||||
<div className="platform-model-analytics-total">
|
||||
|
|
@ -37,7 +37,7 @@ export function PlatformBotActivityAnalyticsSection({
|
|||
) : null}
|
||||
|
||||
{!loading && (!activityStats || activityStats.length === 0) ? (
|
||||
<div className="ops-empty-inline">{isZh ? '暂无活跃度数据。' : 'No activity data.'}</div>
|
||||
<div className="ops-empty-inline">{isZh ? '最近 7 天暂无活跃度数据。' : 'No activity data in the last 7 days.'}</div>
|
||||
) : null}
|
||||
|
||||
{activityStats && activityStats.length > 0 ? (() => {
|
||||
|
|
|
|||
Loading…
Reference in New Issue