From 55ecc443480267f7c7128c1ea9a2f4ec67af63d8 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 29 Mar 2026 22:29:41 +0800 Subject: [PATCH 1/2] feat: add new StatsPage for enhanced statistics overview - Introduced StatsPage.vue to provide a comprehensive overview of statistics with various metrics and charts. - Implemented fetching and displaying of base and model provider statistics. - Added unit tests for provider statistics persistence in the database. --- astrbot/core/db/__init__.py | 16 + astrbot/core/db/po.py | 24 + astrbot/core/db/sqlite.py | 46 + .../method/agent_sub_stages/internal.py | 54 +- astrbot/dashboard/routes/stat.py | 206 +++ .../mdi-subset/materialdesignicons-subset.css | 26 +- .../materialdesignicons-webfont-subset.woff | Bin 17152 -> 17424 bytes .../materialdesignicons-webfont-subset.woff2 | Bin 13768 -> 13988 bytes .../i18n/locales/en-US/features/stats.json | 104 ++ .../i18n/locales/ru-RU/features/stats.json | 104 ++ .../i18n/locales/zh-CN/features/stats.json | 104 ++ dashboard/src/i18n/translations.ts | 6 + dashboard/src/router/MainRoutes.ts | 4 +- .../dashboards/default/DefaultDashboard.vue | 223 ---- .../default/components/MemoryUsage.vue | 147 --- .../default/components/MessageStat.vue | 391 ------ .../default/components/OnlinePlatform.vue | 90 -- .../default/components/OnlineTime.vue | 196 --- .../default/components/PlatformStat.vue | 261 ---- .../default/components/RunningTime.vue | 104 -- .../default/components/TotalMessage.vue | 103 -- dashboard/src/views/stats/StatsPage.vue | 1174 +++++++++++++++++ tests/unit/test_provider_stats.py | 65 + 23 files changed, 1925 insertions(+), 1523 deletions(-) create mode 100644 dashboard/src/i18n/locales/en-US/features/stats.json create mode 100644 dashboard/src/i18n/locales/ru-RU/features/stats.json create mode 100644 dashboard/src/i18n/locales/zh-CN/features/stats.json delete mode 100644 dashboard/src/views/dashboards/default/DefaultDashboard.vue delete mode 100644 dashboard/src/views/dashboards/default/components/MemoryUsage.vue delete mode 100644 dashboard/src/views/dashboards/default/components/MessageStat.vue delete mode 100644 dashboard/src/views/dashboards/default/components/OnlinePlatform.vue delete mode 100644 dashboard/src/views/dashboards/default/components/OnlineTime.vue delete mode 100644 dashboard/src/views/dashboards/default/components/PlatformStat.vue delete mode 100644 dashboard/src/views/dashboards/default/components/RunningTime.vue delete mode 100644 dashboard/src/views/dashboards/default/components/TotalMessage.vue create mode 100644 dashboard/src/views/stats/StatsPage.vue create mode 100644 tests/unit/test_provider_stats.py diff --git a/astrbot/core/db/__init__.py b/astrbot/core/db/__init__.py index a18c127ebf..087aa625bd 100644 --- a/astrbot/core/db/__init__.py +++ b/astrbot/core/db/__init__.py @@ -21,6 +21,7 @@ PlatformSession, PlatformStat, Preference, + ProviderStat, SessionProjectRelation, Stats, ) @@ -105,6 +106,21 @@ async def get_platform_stats(self, offset_sec: int = 86400) -> list[PlatformStat """Get platform statistics within the specified offset in seconds and group by platform_id.""" ... + @abc.abstractmethod + async def insert_provider_stat( + self, + *, + umo: str, + provider_id: str, + provider_model: str | None = None, + conversation_id: str | None = None, + status: str = "completed", + stats: dict | None = None, + agent_type: str = "internal", + ) -> ProviderStat: + """Insert a per-response provider stat record.""" + ... + @abc.abstractmethod async def get_conversations( self, diff --git a/astrbot/core/db/po.py b/astrbot/core/db/po.py index 451f054f62..cabc3432cd 100644 --- a/astrbot/core/db/po.py +++ b/astrbot/core/db/po.py @@ -38,6 +38,30 @@ class PlatformStat(SQLModel, table=True): ) +class ProviderStat(TimestampMixin, SQLModel, table=True): + """Per-response provider stats for internal agent runs.""" + + __tablename__: str = "provider_stats" + + id: int | None = Field( + default=None, + primary_key=True, + sa_column_kwargs={"autoincrement": True}, + ) + agent_type: str = Field(default="internal", nullable=False, index=True) + status: str = Field(default="completed", nullable=False, index=True) + umo: str = Field(nullable=False, index=True) + conversation_id: str | None = Field(default=None, index=True) + provider_id: str = Field(nullable=False, index=True) + provider_model: str | None = Field(default=None, index=True) + token_input_other: int = Field(default=0, nullable=False) + token_input_cached: int = Field(default=0, nullable=False) + token_output: int = Field(default=0, nullable=False) + start_time: float = Field(default=0.0, nullable=False) + end_time: float = Field(default=0.0, nullable=False) + time_to_first_token: float = Field(default=0.0, nullable=False) + + class ConversationV2(TimestampMixin, SQLModel, table=True): __tablename__: str = "conversations" diff --git a/astrbot/core/db/sqlite.py b/astrbot/core/db/sqlite.py index c8e50909d5..fd6668c0c7 100644 --- a/astrbot/core/db/sqlite.py +++ b/astrbot/core/db/sqlite.py @@ -23,6 +23,7 @@ PlatformSession, PlatformStat, Preference, + ProviderStat, SessionProjectRelation, SQLModel, ) @@ -169,6 +170,51 @@ async def get_platform_stats(self, offset_sec: int = 86400) -> list[PlatformStat ) return list(result.scalars().all()) + async def insert_provider_stat( + self, + *, + umo: str, + provider_id: str, + provider_model: str | None = None, + conversation_id: str | None = None, + status: str = "completed", + stats: dict | None = None, + agent_type: str = "internal", + ) -> ProviderStat: + """Insert a provider stat record for a single agent response.""" + stats = stats or {} + token_usage = stats.get("token_usage", {}) + + token_input_other = int(token_usage.get("input_other", 0) or 0) + token_input_cached = int(token_usage.get("input_cached", 0) or 0) + token_output = int(token_usage.get("output", 0) or 0) + + start_time = float(stats.get("start_time", 0.0) or 0.0) + end_time = float(stats.get("end_time", 0.0) or 0.0) + time_to_first_token = float(stats.get("time_to_first_token", 0.0) or 0.0) + + async with self.get_db() as session: + session: AsyncSession + async with session.begin(): + record = ProviderStat( + agent_type=agent_type, + status=status, + umo=umo, + conversation_id=conversation_id, + provider_id=provider_id, + provider_model=provider_model, + token_input_other=token_input_other, + token_input_cached=token_input_cached, + token_output=token_output, + start_time=start_time, + end_time=end_time, + time_to_first_token=time_to_first_token, + ) + session.add(record) + await session.flush() + await session.refresh(record) + return record + # ==== # Conversation Management # ==== diff --git a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py index c7441d09f4..1a04e3a48e 100644 --- a/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +++ b/astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py @@ -5,7 +5,7 @@ from collections.abc import AsyncGenerator from dataclasses import replace -from astrbot.core import logger +from astrbot.core import db_helper, logger from astrbot.core.agent.message import Message from astrbot.core.agent.response import AgentStats from astrbot.core.astr_main_agent import ( @@ -350,6 +350,15 @@ async def process( resp=final_resp.completion_text if final_resp else None, ) + asyncio.create_task( + _record_internal_agent_stats( + event, + req, + agent_runner, + final_resp, + ) + ) + # 检查事件是否被停止,如果被停止则不保存历史记录 if not event.is_stopped() or agent_runner.was_aborted(): await self._save_to_history( @@ -462,3 +471,46 @@ async def _save_to_history( # these hosts are base64 encoded BLOCKED = {"dGZid2h2d3IuY2xvdWQuc2VhbG9zLmlv", "a291cmljaGF0"} decoded_blocked = [base64.b64decode(b).decode("utf-8") for b in BLOCKED] + + +async def _record_internal_agent_stats( + event: AstrMessageEvent, + req: ProviderRequest | None, + agent_runner: AgentRunner | None, + final_resp: LLMResponse | None, +) -> None: + """Persist internal agent stats without affecting the user response flow.""" + if agent_runner is None: + return + + provider = agent_runner.provider + stats = agent_runner.stats + if provider is None or stats is None: + return + + try: + provider_config = getattr(provider, "provider_config", {}) or {} + conversation_id = ( + req.conversation.cid + if req is not None and req.conversation is not None + else None + ) + + if agent_runner.was_aborted(): + status = "aborted" + elif final_resp is not None and final_resp.role == "err": + status = "error" + else: + status = "completed" + + await db_helper.insert_provider_stat( + umo=event.unified_msg_origin, + conversation_id=conversation_id, + provider_id=provider_config.get("id", "") or provider.meta().id, + provider_model=provider.get_model(), + status=status, + stats=stats.to_dict(), + agent_type="internal", + ) + except Exception as e: + logger.warning("Persist provider stats failed: %s", e, exc_info=True) diff --git a/astrbot/dashboard/routes/stat.py b/astrbot/dashboard/routes/stat.py index a6f7ff7f2d..2eb3cd400e 100644 --- a/astrbot/dashboard/routes/stat.py +++ b/astrbot/dashboard/routes/stat.py @@ -4,18 +4,22 @@ import threading import time import traceback +from collections import defaultdict +from datetime import datetime, timedelta, timezone from functools import cmp_to_key from pathlib import Path import aiohttp import psutil from quart import request +from sqlmodel import select from astrbot.core import DEMO_MODE, logger from astrbot.core.config import VERSION from astrbot.core.core_lifecycle import AstrBotCoreLifecycle from astrbot.core.db import BaseDatabase from astrbot.core.db.migration.helper import check_migration_needed_v4 +from astrbot.core.db.po import ProviderStat from astrbot.core.utils.astrbot_path import get_astrbot_path from astrbot.core.utils.io import get_dashboard_version from astrbot.core.utils.storage_cleaner import StorageCleaner @@ -34,6 +38,7 @@ def __init__( super().__init__(context) self.routes = { "/stat/get": ("GET", self.get_stat), + "/stat/provider-tokens": ("GET", self.get_provider_token_stats), "/stat/version": ("GET", self.get_version), "/stat/start-time": ("GET", self.get_start_time), "/stat/restart-core": ("POST", self.restart_core), @@ -188,6 +193,207 @@ async def get_stat(self): logger.error(traceback.format_exc()) return Response().error(e.__str__()).__dict__ + @staticmethod + def _ensure_aware_utc(value: datetime) -> datetime: + if value.tzinfo is None: + return value.replace(tzinfo=timezone.utc) + return value.astimezone(timezone.utc) + + async def get_provider_token_stats(self): + try: + try: + days = int(request.args.get("days", 1)) + except (TypeError, ValueError): + days = 1 + if days not in (1, 3, 7): + days = 1 + + local_tz = datetime.now().astimezone().tzinfo or timezone.utc + now_local = datetime.now(local_tz) + range_start_local = (now_local - timedelta(days=days)).replace( + minute=0, second=0, microsecond=0 + ) + today_start_local = now_local.replace( + hour=0, minute=0, second=0, microsecond=0 + ) + query_start_local = min(range_start_local, today_start_local) + query_start_utc = query_start_local.astimezone(timezone.utc) + + async with self.db_helper.get_db() as session: + result = await session.execute( + select(ProviderStat) + .where( + ProviderStat.agent_type == "internal", + ProviderStat.created_at >= query_start_utc, + ) + .order_by(ProviderStat.created_at.asc()) + ) + records = result.scalars().all() + + bucket_timestamps: list[int] = [] + bucket_cursor = range_start_local + while bucket_cursor <= now_local: + bucket_timestamps.append(int(bucket_cursor.timestamp() * 1000)) + bucket_cursor += timedelta(hours=1) + + trend_by_provider: dict[str, dict[int, int]] = defaultdict( + lambda: defaultdict(int) + ) + total_by_provider: dict[str, int] = defaultdict(int) + total_by_umo: dict[str, int] = defaultdict(int) + total_by_bucket: dict[int, int] = defaultdict(int) + range_total_tokens = 0 + range_total_calls = 0 + range_success_calls = 0 + range_ttft_total_ms = 0.0 + range_ttft_samples = 0 + range_duration_total_ms = 0.0 + range_duration_samples = 0 + today_by_model: dict[str, int] = defaultdict(int) + today_by_provider: dict[str, int] = defaultdict(int) + today_total_tokens = 0 + today_total_calls = 0 + + for record in records: + created_at_utc = self._ensure_aware_utc(record.created_at) + created_at_local = created_at_utc.astimezone(local_tz) + token_total = ( + record.token_input_other + + record.token_input_cached + + record.token_output + ) + provider_id = record.provider_id or "unknown" + provider_model = record.provider_model or "Unknown" + + if created_at_local >= range_start_local: + bucket_local = created_at_local.replace( + minute=0, second=0, microsecond=0 + ) + bucket_ts = int(bucket_local.timestamp() * 1000) + trend_by_provider[provider_id][bucket_ts] += token_total + total_by_provider[provider_id] += token_total + total_by_umo[record.umo or "unknown"] += token_total + total_by_bucket[bucket_ts] += token_total + range_total_tokens += token_total + range_total_calls += 1 + if record.status != "error": + range_success_calls += 1 + if record.time_to_first_token > 0: + range_ttft_total_ms += record.time_to_first_token * 1000 + range_ttft_samples += 1 + if record.end_time > record.start_time: + range_duration_total_ms += ( + record.end_time - record.start_time + ) * 1000 + range_duration_samples += 1 + + if created_at_local >= today_start_local: + today_total_calls += 1 + today_total_tokens += token_total + today_by_model[provider_model] += token_total + today_by_provider[provider_id] += token_total + + sorted_provider_ids = sorted( + total_by_provider.keys(), + key=lambda item: total_by_provider[item], + reverse=True, + ) + + series = [ + { + "name": provider_id, + "data": [ + [bucket_ts, trend_by_provider[provider_id].get(bucket_ts, 0)] + for bucket_ts in bucket_timestamps + ], + "total_tokens": total_by_provider[provider_id], + } + for provider_id in sorted_provider_ids + ] + + total_series = [ + [bucket_ts, total_by_bucket.get(bucket_ts, 0)] + for bucket_ts in bucket_timestamps + ] + + today_by_model_data = [ + {"provider_model": model_name, "tokens": tokens} + for model_name, tokens in sorted( + today_by_model.items(), + key=lambda item: item[1], + reverse=True, + ) + ] + today_by_provider_data = [ + {"provider_id": provider_id, "tokens": tokens} + for provider_id, tokens in sorted( + today_by_provider.items(), + key=lambda item: item[1], + reverse=True, + ) + ] + range_by_provider_data = [ + {"provider_id": provider_id, "tokens": tokens} + for provider_id, tokens in sorted( + total_by_provider.items(), + key=lambda item: item[1], + reverse=True, + ) + ] + range_by_umo_data = [ + {"umo": umo, "tokens": tokens} + for umo, tokens in sorted( + total_by_umo.items(), + key=lambda item: item[1], + reverse=True, + ) + ] + + return ( + Response() + .ok( + { + "days": days, + "trend": { + "series": series, + "total_series": total_series, + }, + "range_total_tokens": range_total_tokens, + "range_total_calls": range_total_calls, + "range_avg_ttft_ms": ( + range_ttft_total_ms / range_ttft_samples + if range_ttft_samples + else 0 + ), + "range_avg_duration_ms": ( + range_duration_total_ms / range_duration_samples + if range_duration_samples + else 0 + ), + "range_avg_tpm": ( + range_total_tokens / (range_duration_total_ms / 1000 / 60) + if range_duration_total_ms > 0 + else 0 + ), + "range_success_rate": ( + range_success_calls / range_total_calls + if range_total_calls + else 0 + ), + "range_by_provider": range_by_provider_data, + "range_by_umo": range_by_umo_data, + "today_total_tokens": today_total_tokens, + "today_total_calls": today_total_calls, + "today_by_model": today_by_model_data, + "today_by_provider": today_by_provider_data, + } + ) + .__dict__ + ) + except Exception as e: + logger.error(traceback.format_exc()) + return Response().error(f"Error: {e!s}").__dict__ + async def test_ghproxy_connection(self): """测试 GitHub 代理连接是否可用。""" try: diff --git a/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css b/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css index c7121fde42..bb882203a3 100644 --- a/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css +++ b/dashboard/src/assets/mdi-subset/materialdesignicons-subset.css @@ -1,4 +1,4 @@ -/* Auto-generated MDI subset – 248 icons */ +/* Auto-generated MDI subset – 252 icons */ /* Do not edit manually. Run: pnpm run subset-icons */ @font-face { @@ -136,10 +136,6 @@ content: "\F00F3"; } -.mdi-calendar-range::before { - content: "\F0679"; -} - .mdi-chat::before { content: "\F0B79"; } @@ -204,6 +200,10 @@ content: "\F0143"; } +.mdi-chip::before { + content: "\F061A"; +} + .mdi-circle::before { content: "\F0765"; } @@ -288,6 +288,10 @@ content: "\F0674"; } +.mdi-creation-outline::before { + content: "\F1C2B"; +} + .mdi-cursor-default-click::before { content: "\F0CFD"; } @@ -640,6 +644,10 @@ content: "\F164E"; } +.mdi-message-outline::before { + content: "\F0365"; +} + .mdi-message-text::before { content: "\F0369"; } @@ -816,6 +824,10 @@ content: "\F16A7"; } +.mdi-robot-outline::before { + content: "\F167A"; +} + .mdi-send::before { content: "\F048A"; } @@ -932,6 +944,10 @@ content: "\F0BD4"; } +.mdi-timer-outline::before { + content: "\F051B"; +} + .mdi-tools::before { content: "\F1064"; } diff --git a/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff b/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff index 415cd06e41145031548829b5f1eb175b684b8838..6131c0411f8b24523ab607486624ed699a608a29 100644 GIT binary patch delta 17002 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 N1^_be0=tuYLWR2O`(Xe8 delta 16728 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�OOk>4*H~;1^KfjmnSzLp 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 diff --git a/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff2 b/dashboard/src/assets/mdi-subset/materialdesignicons-webfont-subset.woff2 index 3364125197e06cac1e77dc6a6afa3bbaa14fd8f0..2ac3e92281b200cf5efa404ebe694d7953545b59 100644 GIT binary patch literal 13988 zcmV;VHe1PePew8T0RR9105+rm3jhEB0DL?E05($q0RR9100000000000000000000 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 Kz7$L*P6GfPwKY@# literal 13768 zcmV;(H8;w4Pew8T0RR9105!+}3jhEB0C~&+05y030RR9100000000000000000000 z0000SC0LI)rll4L~&@;CsB?p}v7J(E6l zM@3v)rBq6#^jJ`Jpc{56#68@*J|@QzGWk;*M`cv*F&Y|4%%A6V^G_g*u;gVi0umBd z?qvuhH~{lP!kWJK7-|te!z8X+6tUwyts@TED!`Lx}BK`ks zwaY*Ne$%LwnG|K3Dt@Wz_m=E-f$nl3rHe`Og*Mv@tZg>3@MqMtU< zfcebX+wK}EJD{&0JLmLz`&yS*v~G%FX1LjzVFQZ-3(`G6i6C^#BHt}2Sg^YP0Lcmc zq?1#wQmBwFaa=@lkbaJpQ~ptoq5AJver^g^rvH~cF57X%fsxITc=NLE=Q(%jc%tz64EAR@O};m077Uu zyQ_ouQ$<1HZtGqNSpWbb0QeLDfIuPeHXyj=FMv}2YrpS2;77#Z;T7wy;&vG5EP}lG z$Oj01lKonwq(UUcorZqVH|4%6^gMF`0g@=-hcp!MN18_7S||{Jj1~w)W(x!%D+Pj) z^#UT~vOoy(R1k{16oerk1>wkVK?L&m31*MmTn=)D=8HlyiczOdJO*Q? zW-SudMS(<0cL5dUr2;kOm4YP78wJUfp@J03NI@#)Y(X03!-K}>zI>s#27PIW3{o=L zV#LZ7Cyq{%BzaP#m@Q43IWlCLOG_(Xo;(E%3O77{TcbsbCp_;3-Fu0Gb@W#XdgyNy ztfvnY^wQ51Y@m-6JV|#IJVn1$@HG8O!87!02i8|^y}17&gu^DaYCWq#gUuFOY>N&Z zwr(YdZClO3wly4{+cO-V*Qd`51`K%7pg}L~T@EkrLk_PvZ_KMaJYL_I9NzGaZ(n`y z2wzd4U$}5PBulYVx^!>Kl4ZB0mfE9Jr?+(LHlSCpz54Xo$H8I$PEv3{@k+r#CDGxK z*=9SeLkICzb2zd!90vC;2gly0fL9iEaLSS6=pN^A%uYKUcf=7V3>$WG9t!@gJgmSe z<=F*JD}VAZ#Ln(*Z+g=^1`T?5A5-w2@~Z{!EC0ISjPjok!=~IdvPXP^vnr~e8o1U; zfl(D3K7n&8=RF1ZV7(OhSml!iK2dq2;8T^~6nv&?X@T>qmK7LNwYT7cs{I8QRlQSi zN!6zXmsNdRa7EQW1y@x+Rp6TH&komJcG(Rc9^;$j6L{1de@@`0Aw#~{BMQD$t1R%9 z+FuH8sr{?Kt1j^O8#7V{2F;gHfO(Ewx(Hbr8xVp1Q;&fzeLkfR_lMGMT-m zT5oVJbKu;{6y#=Hy)`+80lWhdud!wkc2yu(pXy#zB+hO_C+x0wqR;UnDsE@~cdlr` zWneTU|N0y|V;kWji?-qF6<1f>0kUV-=uORn z@hm$eE0$nmOIHXrNELX*pTl4~^q- zdvtRE`wauzR(o)0yN`wQp|<_ve=hl(pNC+pR4lu?u=_@7pZ<3vRQSA(EKI5^nU-+x zt~-{oX*-TWKxu;mTjN`U`bIiwQ?tRwC~PN|tpwLRyXF~vCY99+Er4Z$m7E^X78M%S zb!5PREz5yjeF+^*pagoaKr;wqu&SB~c!{^pDNSrN`365@d44x7@cNL-^o>bzzY@Im za_t!(zLL&)1LjaanD6b6)+g#j0{}oy5) zb;3hky@Vm)U_2&?6GdicM&+XN23KVsPnzCU>!~eb|mY#H0*KtWy8%?#bi(Tx(aX*2C0~Fi7wqNpNGK?E?YvnbNE#giaY- zzp6f^WWqVuix?@|VUJlv)ddQZKgSM3Gm3Oa+RgBNh)Ntozrm-3g75-^t4Y0phoyab z4RCErXj%o{JbW>H;o4+ky{k?1Q?=>jlvz04;@IB_t`WHN0p$0HX{IsKSB)z1SiR4d zTGy<|E^eJ8MhSaNfgsPV&~s}M&e|#@t*05bN!^gj%k>tVzv2n%5d5xFG&d+?#~j#c zyVfu!VEc_h$~&PQ55b|W4xymLwMV1$L_?Q_T~-07vF~)vj@@GmA0L6G(|E*_OKcbp zDY7(M)K>CmhXOFU#&`Mk;AO`gIJ?%6l$RUU<}2sLy1Djz#tsRgC9Z><5)(%{bHSAi zT;*~D88aN&PtU$S&4W$ipH%yc-e?SRs`l<&9HVl++%|qF5<2-)Osc%^3{0iOra$?G zPYf8AWDCV4q+i$_=}ST!0*4GMF^n8Kg8l)KDjG8d7Uf2Ky|G^_=J)- zQDa3_9pze(v8V!`6N27DPklCoeB+2gzI{rlaun+lmdoD~ z?QB{&^p&0n$md66R9^3y4>X^Ai0H$0kk%!k{jZVY_I;%c4Xa1aWTdkd+ky`Rm4S@h z&-J}D!xPgBWc9EDe72Y@D7__MHc~)#5f>lQMQJ>iOhbt^t(WI(AV0_fp>vPmxbc!( zyI6?#!shymhz;u;GlmFPhC=<-W}(}{GhJKi-TYgFcInxZ!Hw&RvN$65t)Z=uj>-2q zVs{H>nEa!JIYWAM2hRNg+c9=$8u@68X+C+nken$KS*HcLPK(t_hZ{>%`{7A6VApXkVI zH25H9xO7MuLIET+Y=5eOt}%$Oyg|@{kuSLrN6W`4BagC;Kz?pFnv zsIbu5U@yTi+ zU4^5MxVRukYRwbQ|4Q38utrSgDIy*?g*`v&ZH*X`mW?6;H-EEwQs0eD%6kM{eW-&FV(4Su^Fnnd0QHA1cU4a42xcg5(&mWIBdg*~QUO zFkyn=KqS5>dzNT1ss=R*LBk$QlGIf(izkz7y=mJiicTScCdt7Mq6`bRFwLQ`2*pq& zUK*Mj&s@T|^%9yeYKZ&z15YY>464v8*C-;Sf410zpNP##5&~Pe9~ewKTV=5q7H~OC zc59s0c+W_`z5KgpNkUf=s!6UR0MW!o2#_>x8o)N+J&k?6t*(9zzyE5#+vjKK_<@V#hc1pCIzWQvyx4UAm4t#%ii)cRw0 z`4v;CELtOc5QU}6Y7!R(mPi$?K4sIg=tAoRwk+9O%D(L3=iAS4&E3wmlj{3|JDx&& zHoxyKDq;2?7e`yVnCH{ZZQ*g@Rj27>y`$V&wM1!n2#5leMw4L3642wcuEibgJxqt+ z15r|$S#X7hFxMsIpf3kcWX2k~gBXg(&{@fc+*o;of6&hjVq7fav26kS3gnm=lUArR z?pp!cwBhMS$u?5WuA-hct`^|g6|E4@OIdJ9AJBqKs~0=*Wo3yJ*Yupg@@F<}^j_lo z?>*gG`8%_?T}ZnPVeZELXPF=a?fIbHo$oYS657wt++V)FbT8l98Yy^9vAO&AF#I z>7wEVV1GaR>XRRT`t;l{KdWj8W-%O6doBo8Bt~1ZrXBe~@dJz`ATJ6OTgf~p0D-EC zNOA)4h@?N^m?T(e?*bhA1TrgllWp+E#W;M=(X+H&D0%;7rQZV@iON&Nx1pFXOyVC# zrxMc-09lWm&kESRj$E3SdudE{QNeY= zFDM6n#kT}(5KK1(fIMz0q2DWI!aPmtXFI9G7_5h1kG8f(wE1Qs=*t|D*s*sPn)lO+ z-=Y)brlC>J-%>gNp2lKt^v+4685++Gh$L-EJ*$svQw-X6^k-GvaiRXKp;+QxL+m>3 zDq7%(s%H|L5j0fldXnP9w_5ohU0-5;AXi5+72lV65Qsmu3&)=MBV9f4W;6Fo;EB|8 zg;F|I_B?^OuJm&wd)Rc*_8Yg9pHt<_2TJK*x)+Y%x9@5{Wh&PWR!4s9jUF}Z45_~% zoz_>3y25@N7wMocop)hA>&u!X7u~FLK_FfT1(sCte^rPK>>Q8HI3U!3@A zx^j>zZ@zz-2nENYvD>x;oUcu&79D&1(2ssl&ObDbn->*gXS(}AUI+KSLzox#oPjm_a6_MO5if$c)btDXfB@Q)j|;8Rw0rypvTt`Z#oD8GkeJ00s~PH~r8qdH@ww z_{@Mf9G+cbpO|sxfw`9j7u@fhHwOkV1TRGvDp)E^K?4KUJ_dXOd)bVok#~n(7cZDo zbbo$>(N4CJb+bZ0vncmA@Px%CBfpb37_!{4g ztAnO6FjUM#P!b1XbV`1{FCaw~)%uqK&yQbHv@_ngzF$ zMgfnq$i>(y>=;JmJ?qdS&*$l|wQ@C6n_542`~EV>0`<(<-KMB|j?5>M;)ik-8|3HE zc*Hy=EFOc)z0}42J1n17Dmd3MSScP9LW+%A1)>adgTi=IA<|>P$x>Qm1C1o%3~2nb z`6_im@8dd1n|nK@;qW0GcMHz{1dVK^08`+y$P8ol^_X$i)ewOJ#C|wTkJ_Ly2#gU= zx217Sol+H5n`Jhe*s7db6@$Jo9=(s_n65t&DzJ4AerS(#G(a}fv|Lq7Wu;OoR%bl6 zdaC6)UJJaK6{bD|#g+}JG-?hEzQZ_tRN2Nm$^)Li9;Lge{D}W!GxdvocrD%PVPJa3 z_F?;IC$+6N9-3%bJ3~5)9pK1_c9v%fjcg-1&yw4P_N6ty zbs<(hg!kVgdLvZ>=@JCx*XPUaKD_8wP#LGsl39hiAiud#yAu!LJtDPGzGcY4v64bf z)2-dai3US~B{U;X4#JU-kP=D!4A&A|X*Gr8E_yGUGA@M?J!>0>aScvzRG6YWnJW3y&}>j zW7?%_Ptm#8H8|&^lLKjlpCfq`+G`75cAvnHKiYI3}n2VfSto; zq=Ep{&@)TmWsMK|IE+Y7sy!p@OY&L^7PR}0;D>BsV#E$e&0`3pYbt90?-72m95u}A zgz?8X(f&W^pQQQ;F1?;RM|lrS@FkRJLSF+>`~1vLKYnsn94mYlHBm~G;9gUWXV%C) zQJBF|o*Fr5*HmLttz>oKgHJL)c~H1oQfo{~$yAth#d*a#(+O0QFq}|BCDaW6aq9iT z(7ZWqZF9g1vLZLa$Y=3P^o19A#39cLQfYBK4uZ-M2+ivEN{e1_F^JKy7-2{bL2;3H zkRp%F<+Q@Xj`5O3M^b zsbH7bmbChAgX7!0y}iM@Yg@NwEZ`2D^QDw8=t_UZgzYccA69@f6!AabxC{_WQKUL{pIUkL?yl_2nv9H^ zOw#(+fTk!41keQ1+cAIMhl_1u6vSm%I$I-jrCyKUPPs5}>LA52;#K~)z;S1p4rd<0?iK)V); z5Q0c%i?_0J-@e<{EE9OgA#TzLfLjbLn&1%+Lv9)jFT8l$;b?7r?mjxmCFDpBJ3D$= zn)0@_#kWN(LP%xRm4W~b1aSGUgjbCr<; z>FZrbg@41KQjm4`2DLjfwwkM`g7@#@?(2B=lpovK>HV`)NOnoK$v6vTlQws4L9U)= z^v1yCw4SteJ0rc6o#l;$zKfQ&Ep6c73-Q~T-~ncvSwysdKf#2}yAZbG(=#fq=lAK5=I~>CpsxK2-K@rV2NlysGF@ou%jB_ zEW$|RqB)AweSMHb9!0v5n`BY*M93g93C0wBm<6f|Q{HN3EFT}{p63pmH#WzG)UpGc z6eR)(<*j!U{AV2De>Fq#SR_=|DG}7h>3$>c*vv2fw%_`4femW2|6N+b_x8R|C8nk| z-t9HNYtvu$rtXrtHO1hu0$B6Oi7-j ze=G9&1Q{u_=;bmwygs9g+V03IN_5zw7kW}YxK3ShhpReU+NGq#FoBe4Alj8V<4MW^ z&A?0pYV{;}Go0I@hBTq3W-5}HdaIV!_X@yk2G$xL_MSQKEf+pE-1Nm@$OSRY_ryFc z*s!~V_$&wqeMXebI|TLs1|8<`gpa`th)j@fyN{tA;F9>xJefm7{ug<(rIvJ8ZE9@d zAv*4sU=qi7%L#xaLo=k=EbDXfS)Snm-XTf@v^X9eMi`&`8aTv(M`oPCXQVBN;?9i? zj(5rvJT+RpAKAl5O#XR!5W)y^Cq|Z`2yv`fp^8<7v5{Uil-QQ<7nC7l6DrG_I22CmT+<*W@1Al*DAh5N(Z_r;E(hC8zp z8|Q=)YXXB0+*fR!v-MT=YqOxBMd+Ny#5un#J$OqoFQ9Yv{koMUNRw@&?xd;E>CEm- zRsQ&xGA>FYiHcJ`_MHh^KT)E6pN$Wf@;wrflR{I-dankXUEar*ZVLQ*~8 zsSz#5a26+d!xdws6F^YBeLUjH@kg6ggpmhvNb1s~EahK=QknK$f4&}Z2t){5l1+^| z9rTBWERAZvrbM$}rI{OjSVl86$-pUA{%`lkMVN-#l^Xl8-Z z5lzvd^1stVi$HTF2Y5`Kt%(@*3l#< zlGn3E!y*n^_Voo%25AX-pNx}jBnG%W&CA&?e0zo#kBAwg$76&E6OAiZ_Wbmd))!*isnXFp)n+nLS5v}# zFJgL-iWY;CI_Q7R)qEMkunfn`_ZsooVo{hBn8RgZ*)R7By$5pYJE8VdPQF*0k{d`9 zX0tEF33^Fx$|_b_d#|NS;3@|$;mR-~>_g`b?b-P?8#mTyY#Ix6G~cK(c~fCAIPz#W zMdK?78-cGb!n+`=L861_asiRU9LXVMU=Ac%L>MCd8eW*;6h~2*0BWbo?}N;EbYdq( zL1xZ8nt`EUP!u;eiDGPp00^1MZX-9SC`<}2_M^pe?V0(6D|yDC&VUTM!5PU&7U*3i ziw||jgLs4xIPXCo4X-dvjH|9(@g`dpwj&B5dtI0dx@`cw?nw$yN@tEgum82TJTykeW z&V;KyX)#T}-dS7QaH_6ua&ig*1{MJx*rQOfSex6_n4`sgrYYGIY55=6?tWZWw*0QG z;B=a3SX7BwWqDgVI=1AM?f=~i6r6J8L2@ULR&gqB6#_pU-RU#sBb1hHjR%2vjzhCt zC~QVC8)!M}f)NxJ0=EP{j;H%%EGJ@cAu1Urn^DTA)2HRTH?LPl3uXzTmG!-mvm#-B z{YUAK03$}2cOV+^G~M0+vl@&L!Um?Xk1`n14mg8i>46xo?XvuI(XOP+>7-fr55DGP8g@BN`FWV$U;$MuPd-!T=tjR9RFi=Lw$- z%f@90U}BVNW}2Sk*Em`fEXXf}?8si0jE^8k{df3iqJ9@z3_L+yPIX*qLB){#~PW!_X5AHKpLI(1I6bH8TzOfNf0m{|4$ehSA-U8&{~A|IW(W=e|^hUc%EWObq9 z&~vIl2`cwnlWmHZ_XsH=;~rf-@_1AB8b4Gn2~?fSe_m?XBsgBxI<8IT4`0}!!+WJi5sl13nuYG7p6+Bn5sP9x5)0~&60z1CTu zSgfg2pRHBdpJ?9^mj7Y|1vJn-1<;aD)b!$@l0s?A=F_SC*^+k9(%MEVVExie;RqlW>8s(d2=!iLI!swh>mb?KOK=LCMW@oc>ABvk;my zNBwDAOS7!H8Q?LFv$DmYgN#P6%R6BFAR2+-IGT*o+znbqV1$=MUz&OlNEn*aB)cSH zmRvGU!cM*+*(rG=56B2HfviM%UUK|HK(j1T5|MffwhwhYSwbMTiCqYh2qICmxAVmp zye#jcxp~>v#>&Nu8!M-J^Ej}~IXRwnjyrGMFr7}U0UAJ*gZ~Dv#PQ}J5Dl|b1G7wP zGut@J&G&&RDr&*q1yQsZ7D`%DS{akZtXQiW)c^UD%G#cimeRhHquu`ASi{)r zalPIbkrg-eXClA&Nog9QWyAWS8yWEvSU;#b=zq2yS#SV=(O!szG^k+l~ZBia@&^etYP62bVqwUIRk z$LJ&&D6R>J@RD-Cf)$6A&F;_b-*Q7cutKb~GSdN~hwKe^oEwU`YC+jiXQ z!)ynOx0@3esTM&XzzkQnj_QW8+40)FSj^zqd#^c~Kf3t;Mqln|<~g(Fxj1W_#g=$g zQXj{^hQx;OvhK9P${JR2#zoAn6KN+Rhw9^?x;es5A=&a&wCdfU)+&=&k$)I!3AVo_ zv%_{MV>AjAQLU^CddD|2(5}a)7L&R3+`Iomxxp{K(CMXUo`;>jrFWX}VaQBL$0O#R zQu(uatE#3U%YA4M^+G~~5mdM`>TAhBYC?^m{pN`9-r_IfEhl49^4w zH+TVE?0@t=z>h!X9&0uJcx`Q6#FgVO{=VgG%kM8n%6p*3%MVY!68_Lt*rm@fy;mVGjQ|cYqM$}feJKxl%~+UnUYaF;?L5}eX!Xqp5L|0;VAKG zp?2+BFKBTX9O!oJQl2h{kT9cw45Jr8jZ5Ynao$-3g(eW}Es@h63M`KQ4~pycD~}$n zuJKxzpFa;OW2X!|-KUVJnbJ~?8kuNJM513N_vfkdn55wVZ%UIZ`c8*^GOF*$r_=Ao zfoXnI8^X`moENI3mEs+eN+IoVwW}m!&O)rZ_LIZ3G+Nlp%3|80+saGZzQ0tA@y#Ji zOL_X8mOP8kYl&7bmudNcy<`6R&!T|ys~}QsRrZvNHVf0ME)1O z)I!agd(F16HKRUpSlun(vh#lfTF%*Zm=1J-SQ4LrkpKAB@eF=>*4u?`pbq&A0w_}? zm@SY_GK8gHd5hU>Q`nX*TL>O8k4BL`1|#el_aKBAnldc~6reO^DDU}U3+O$f4 zFtcx~d2CPDp5(vWAAL0Xr*2PUkFuw6kM2+R#~-=>O5W4e12g)_Z{yqP_)*xL{LrZV zKwDokIL`Rz`WFNSG9SHHqcjMvCmDZ<2>->He9h0Gto>}LF(9zOKi9AE!}n|Jb?0=( zhvB({5vn+KTy9(r6;&%1D<#?Mf0^qqObHOqeYiebq7s{$ z^7$o1tq3gaN;12BS$~5xP`jger!mY3z4z_jt<%B5x;n~-&y6r6&jAtP;`b9wXrncZ zhmpAuRgwoQY4i!go$nPMffWH7FUwOcsP1iO=*RYjMVXyV+1dpQvkE-=+Zbko&#=*Y+N;WizX?nJSDK%|o$Ii}~nzUKX znT+Z7&f@q~ZBuSe014C|VF-vh47W2k$srCd^dH`~&0H}(U2cYl=Yp%|KVmQ47aD?y zV;_Fl_W#IuRv5|s@Lq6K-;VWXuRK3Jy1iI9bUi}ecIDv#fwWLz5x+Vj9u`IHS#z%l zrk)lH0uq)4kBP^ExjVrj0V8+)HwWMJ`>r(@{PRH<)DZzZ{hbI6zJ&7|>kQ2I3&>wT zxPIg8%R$Vx%#_eIY~AmhdTqH;0rG?PV9D&Z)d4|i^*6KY=DeZ){b13+*mo5R-w!^@ zEt#nf5sit5#U(ibqVu2$%O7MIewgO{I6t6&DWT%dCV{R&PiEJA*n`JugNvbK1))kj!NkJ)~@a53X$cF z#Xfe@YPB;gPf@y3R*0wK=AJfK%C8t0P0S*q0}FRnL;f3c-pDu1lc(pJ^Oat_@o=Tp z`tfaMo=+qb&u3;H6_etlnGjfJ#c~-Mbt*Zw%6Pp;=0SJd*p1K#B4F&q9tePGjH)Da z6NcL_16dia3QttoF*=o@LIQ0E<#}xxbE+3BP&Y9zb#DEehIO&^uJ>u}MiCHO! zo85oV1x4Xx5ZoY+MRtxu93LputHM#5=V`3_Uf&06cG|ii1}AnR4s-As+@n9vsPh^- zf?lex_tw{a9@t^LSUY3!=qMv*U{#Bf*iS#3|Ix_wxWB>B+=3gtZwj7QUH|k`Ku2SG z+>Y#+!yH9BIj2K2J}!2l9$jLYl%(lm5`j@MRP9POJW-)ih!t1;5kv*^{KUr3k_X*i zx(CU6m5|wxSPa3JDJ{G~rn#eTi6q=0@*wd}EGb4CCnw)lLx?Vx-$ln7l*Ob^Ys@tF z8KP#+Y@qA+FL%o9V9{Ao;eGlUC3&a!&nczucwyh`2JH;G7qla+h*MR~2^A*t4F$<< z2b#L|@y0W|VI(P#6+OFh#5=O;S!~7J#iqs2K)zR4XF(Q-!FGi!qmvRVF=>EsJZE)k z^k~7*eX1(3a%Mw``1mwshS$QV)&jS**u*iPkNJ@&0r*F=8 zb*hkPtE$sAf5kY0LhH@e=4lm_=#2D?1WC9GG(l}of$prUqfXY>PyAX_x1I5@H<}RA zdeY*4G8nu?hLwsm69M92&@B7hV^WM~sh^Tgztzr+Ye{Lc<%k?AKFnedJ~MECqV$|U z_Hqa<*`~ULkxzNvU; zDDG}kpC2sN6#;tD9GE16I6)$ElCXaPiRS=`kAU)K^2ry5TO*R!Y>En6`)*LdP2c6; z5}NtFKRDnJTn(sM`ueN#bE)Z!DMysUt3y4X+cLWquUasmW68m{)ZMHh&qOOyV(82 zi12^3277rg*3cFu5gC2mtaPX+5}$K0QJtqv@%vPaFJc1@wwM?~fDhWabZbhxq^wNR zp3+Ls)jB9u_ROQuMQn9LK3E@-Fbo<6jx{KrX|0jYcv5nyMc8weDniEKb z+zlBs#b z5n*IUbDp0O*((pmV%x1nzg`wo+qp1<82n0cFRt1<_-le6LyTGgr~0?+z3t!JymBRF zGI{S(Q&}5kumtv4o!Y4>dRk5sOr$sbx^OIyp@B=J#kEGuwU+)U^5;#J1Z+gC`ENa( zTq}db);{FKX<>Z+?Tfd(w{G3`Uc7x_onG5K8nTF-vr5%Iq?RnvN-1DFvitsqG%UG2 zJrwPW9a3sl1=^&#MsBv^;Oqfvtu9PbKRzes!^E}e{O>UugZ{qWkeZTEH85MaE--RS zp+SG5LKk2Vb_nO5u%9>q5qFNZ z0gu~{B%L{P_|WiJl9Tayebheh@P%Qo^Ph4{)nhb8c`1s%kaTqT7VyP_r;27&6wP=l zcUDk@WYzztLw^aJ)m#zk$SIPg8VSp>bh$Uh^yRrh&99gtrL<XkmzCb0wez}#KdY%_18pXM*Wvht*W1{f+kV;`7X6*TGpFSVt zXC$=#!TvQ&9g`cA6QhD7&QUPUSJ^YXWnY zKK2@~$0YfuCLEYmQK5=n6ur3Ee+GvNJpCI9V4>@bpWCdztKi6~@8Z z>tvP(J&3uMxktHI>F*I+sng+7*_tYM`d)Xr@CRu{KsoA03Szy_A?1F0IU>27cBWij zvSvS~1%IQQUYzT;|3=tNifpaf+OK$e#Ut-30SVP{t=Dlcv5Z*PdQW^vM&hMlmK-g{T4Z+) za4treET)ZOmwxcsb3Lg}4;g;PMv)B~WlmhAY#qVcB&qE zFIzh6D27{?xwRMiR)#*6z^-n?VRVCbl3@NK639EqE+?nY8m%ZZt#V(g?5Gv+Sj;qym#ka zUc$`~Nlm$(;$8uSd^49$hj^qiQwsU?i*9f9>i7%|Z-kx7%q(8LO4eSN-l6fWZ;B7Z z=h9x^A86&Wa!!WseQQCtetv(qmRA^DrteZf~g-@V#h5CShuqFU-Vs{)hCt(@XmV^n{RX_nj?}IUxcrf&eY1zFra}OHXMiRvP9d5h@yJ^lW{g+O|9UZXACY yXGWZOn|tWOq+ORDgnTUtW6DHmk(oN=}duO&2MXQuootIXf_l0 import('@/views/dashboards/default/DefaultDashboard.vue') + component: () => import('@/views/stats/StatsPage.vue') }, { name: 'Conversation', diff --git a/dashboard/src/views/dashboards/default/DefaultDashboard.vue b/dashboard/src/views/dashboards/default/DefaultDashboard.vue deleted file mode 100644 index 4037a00458..0000000000 --- a/dashboard/src/views/dashboards/default/DefaultDashboard.vue +++ /dev/null @@ -1,223 +0,0 @@ - - - - - - diff --git a/dashboard/src/views/dashboards/default/components/MemoryUsage.vue b/dashboard/src/views/dashboards/default/components/MemoryUsage.vue deleted file mode 100644 index 23c183b7ba..0000000000 --- a/dashboard/src/views/dashboards/default/components/MemoryUsage.vue +++ /dev/null @@ -1,147 +0,0 @@ - - - - - diff --git a/dashboard/src/views/dashboards/default/components/MessageStat.vue b/dashboard/src/views/dashboards/default/components/MessageStat.vue deleted file mode 100644 index a41c664e45..0000000000 --- a/dashboard/src/views/dashboards/default/components/MessageStat.vue +++ /dev/null @@ -1,391 +0,0 @@ - - - - - \ No newline at end of file diff --git a/dashboard/src/views/dashboards/default/components/OnlinePlatform.vue b/dashboard/src/views/dashboards/default/components/OnlinePlatform.vue deleted file mode 100644 index e634bc3082..0000000000 --- a/dashboard/src/views/dashboards/default/components/OnlinePlatform.vue +++ /dev/null @@ -1,90 +0,0 @@ - - - - - \ No newline at end of file diff --git a/dashboard/src/views/dashboards/default/components/OnlineTime.vue b/dashboard/src/views/dashboards/default/components/OnlineTime.vue deleted file mode 100644 index 66f8b80121..0000000000 --- a/dashboard/src/views/dashboards/default/components/OnlineTime.vue +++ /dev/null @@ -1,196 +0,0 @@ - - - - - \ No newline at end of file diff --git a/dashboard/src/views/dashboards/default/components/PlatformStat.vue b/dashboard/src/views/dashboards/default/components/PlatformStat.vue deleted file mode 100644 index b61b1b298a..0000000000 --- a/dashboard/src/views/dashboards/default/components/PlatformStat.vue +++ /dev/null @@ -1,261 +0,0 @@ - - - - - \ No newline at end of file diff --git a/dashboard/src/views/dashboards/default/components/RunningTime.vue b/dashboard/src/views/dashboards/default/components/RunningTime.vue deleted file mode 100644 index df83bb30ff..0000000000 --- a/dashboard/src/views/dashboards/default/components/RunningTime.vue +++ /dev/null @@ -1,104 +0,0 @@ - - - - - diff --git a/dashboard/src/views/dashboards/default/components/TotalMessage.vue b/dashboard/src/views/dashboards/default/components/TotalMessage.vue deleted file mode 100644 index f9547c3943..0000000000 --- a/dashboard/src/views/dashboards/default/components/TotalMessage.vue +++ /dev/null @@ -1,103 +0,0 @@ - - - - - \ No newline at end of file diff --git a/dashboard/src/views/stats/StatsPage.vue b/dashboard/src/views/stats/StatsPage.vue new file mode 100644 index 0000000000..294d801474 --- /dev/null +++ b/dashboard/src/views/stats/StatsPage.vue @@ -0,0 +1,1174 @@ + + + + + diff --git a/tests/unit/test_provider_stats.py b/tests/unit/test_provider_stats.py new file mode 100644 index 0000000000..c306c3f820 --- /dev/null +++ b/tests/unit/test_provider_stats.py @@ -0,0 +1,65 @@ +from types import SimpleNamespace + +import pytest +from sqlmodel import select + +from astrbot.core.agent.response import AgentStats +from astrbot.core.db.po import ProviderStat +from astrbot.core.pipeline.process_stage.method.agent_sub_stages import internal +from astrbot.core.provider.entities import ProviderRequest, TokenUsage + + +@pytest.mark.asyncio +async def test_record_internal_agent_stats_persists_provider_stat( + temp_db, + monkeypatch: pytest.MonkeyPatch, +): + monkeypatch.setattr(internal, "db_helper", temp_db) + + event = SimpleNamespace(unified_msg_origin="webchat:FriendMessage:session-42") + req = ProviderRequest( + conversation=SimpleNamespace(cid="conv-123"), + ) + stats = AgentStats( + token_usage=TokenUsage(input_other=11, input_cached=3, output=7), + start_time=100.0, + end_time=108.5, + time_to_first_token=0.6, + ) + provider = SimpleNamespace( + provider_config={"id": "provider-1"}, + meta=lambda: SimpleNamespace(id="provider-1", type="openai"), + get_model=lambda: "gpt-4.1", + ) + agent_runner = SimpleNamespace( + provider=provider, + stats=stats, + was_aborted=lambda: False, + ) + final_resp = SimpleNamespace(role="assistant") + + await internal._record_internal_agent_stats( + event, + req, + agent_runner, + final_resp, + ) + + async with temp_db.get_db() as session: + result = await session.execute(select(ProviderStat)) + records = result.scalars().all() + + assert len(records) == 1 + record = records[0] + assert record.agent_type == "internal" + assert record.status == "completed" + assert record.umo == "webchat:FriendMessage:session-42" + assert record.conversation_id == "conv-123" + assert record.provider_id == "provider-1" + assert record.provider_model == "gpt-4.1" + assert record.token_input_other == 11 + assert record.token_input_cached == 3 + assert record.token_output == 7 + assert record.start_time == 100.0 + assert record.end_time == 108.5 + assert record.time_to_first_token == 0.6 From c1cfd05fa0b3ec305f92e37c0dca0ed81567cfe5 Mon Sep 17 00:00:00 2001 From: Soulter <905617992@qq.com> Date: Sun, 29 Mar 2026 22:42:12 +0800 Subject: [PATCH 2/2] style: refine card styles and remove unnecessary shadow for improved aesthetics --- dashboard/src/views/stats/StatsPage.vue | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/dashboard/src/views/stats/StatsPage.vue b/dashboard/src/views/stats/StatsPage.vue index 294d801474..ca6ae1ec97 100644 --- a/dashboard/src/views/stats/StatsPage.vue +++ b/dashboard/src/views/stats/StatsPage.vue @@ -697,7 +697,6 @@ onBeforeUnmount(() => { --stats-border-strong: rgba(var(--v-theme-on-surface), 0.14); --stats-soft: rgba(var(--v-theme-primary), 0.08); --stats-soft-strong: rgba(var(--v-theme-primary), 0.14); - --stats-shadow: 0 12px 40px rgba(var(--v-theme-on-surface), 0.04); min-height: 100%; background: var(--stats-bg); } @@ -707,7 +706,6 @@ onBeforeUnmount(() => { --stats-border-strong: rgba(var(--v-theme-on-surface), 0.18); --stats-soft: rgba(var(--v-theme-primary), 0.12); --stats-soft-strong: rgba(var(--v-theme-primary), 0.2); - --stats-shadow: none; } .stats-shell { @@ -818,15 +816,13 @@ onBeforeUnmount(() => { .stat-card { border: 1px solid var(--stats-border); - border-radius: 28px; + border-radius: 16px; background: var(--stats-surface); - box-shadow: var(--stats-shadow); } .stats-page.is-dark .stat-card { border-color: var(--stats-border-strong); background: var(--stats-surface); - box-shadow: var(--stats-shadow); } .overview-card { @@ -1168,7 +1164,7 @@ onBeforeUnmount(() => { .provider-list-card, .token-total-card { padding: 18px; - border-radius: 22px; + border-radius: 14px; } }