|
1 | | -from pydantic import BaseModel |
| 1 | +from textwrap import dedent |
| 2 | + |
2 | 3 | from aiogram import F, Router |
3 | | -from aiogram.filters import CommandStart, Command |
| 4 | +from aiogram.filters import Command, CommandStart |
4 | 5 | from aiogram.fsm.context import FSMContext |
5 | 6 | from aiogram.types import Message |
6 | 7 | from botspot import answer_safe, commands_menu, get_message_text, reply_safe |
7 | 8 | from botspot.components.new.llm_provider import aquery_llm_structured |
8 | | -from botspot.user_interactions import ask_user_choice, ask_user |
9 | | -from botspot.utils import send_safe, markdown_to_html |
| 9 | +from botspot.components.qol.bot_commands_menu import Visibility |
| 10 | +from botspot.user_interactions import ask_user, ask_user_choice |
| 11 | +from botspot.utils import markdown_to_html, send_safe |
10 | 12 | from botspot.utils.admin_filter import AdminFilter |
11 | | -from botspot.types import Visibility |
12 | 13 | from loguru import logger |
13 | | -from textwrap import dedent |
| 14 | +from pydantic import BaseModel |
14 | 15 |
|
15 | 16 | from src.app import App |
16 | 17 |
|
@@ -65,62 +66,66 @@ async def help_handler(message: Message, app: App): |
65 | 66 | await send_safe(message.chat.id, help_message) |
66 | 67 |
|
67 | 68 |
|
68 | | -@commands_menu.botspot_command("stats", "Show usage statistics (admin only)", visibility=Visibility.ADMIN_ONLY) |
| 69 | +@commands_menu.botspot_command( |
| 70 | + "stats", "Show usage statistics (admin only)", visibility=Visibility.ADMIN_ONLY |
| 71 | +) |
69 | 72 | @router.message(AdminFilter()) |
70 | 73 | @router.message(Command("stats")) |
71 | 74 | async def stats_handler(message: Message, app: App): |
72 | 75 | """Stats command handler - shows usage statistics for all users""" |
73 | 76 | assert message.from_user is not None |
74 | | - |
| 77 | + |
75 | 78 | # Get user statistics |
76 | 79 | try: |
77 | 80 | stats = await app.get_user_statistics() |
78 | | - |
| 81 | + |
79 | 82 | # Format the statistics nicely |
80 | 83 | response = "<b>📊 Bot Usage Statistics</b>\n\n" |
81 | | - |
| 84 | + |
82 | 85 | # Summary section |
83 | 86 | summary = stats["summary"] |
84 | | - response += f"<b>📈 Summary:</b>\n" |
| 87 | + response += "<b>📈 Summary:</b>\n" |
85 | 88 | response += f"• Total Users: {summary['total_users']}\n" |
86 | 89 | response += f"• Total Requests: {summary['total_requests']}\n" |
87 | 90 | response += f"• Total Audio Minutes: {summary['total_minutes']:.1f}\n" |
88 | 91 | response += f"• Total Cost: ${summary['total_cost']:.4f}\n\n" |
89 | | - |
| 92 | + |
90 | 93 | # Per-user statistics |
91 | 94 | response += "<b>👥 Per-User Statistics:</b>\n" |
92 | | - |
| 95 | + |
93 | 96 | user_stats = stats["user_stats"] |
94 | 97 | # Sort users by total cost (descending) |
95 | | - sorted_users = sorted(user_stats.items(), key=lambda x: x[1]["total_cost"], reverse=True) |
96 | | - |
| 98 | + sorted_users = sorted( |
| 99 | + user_stats.items(), key=lambda x: x[1]["total_cost"], reverse=True |
| 100 | + ) |
| 101 | + |
97 | 102 | for username, user_data in sorted_users[:20]: # Show top 20 users |
98 | 103 | response += f"\n<b>@{username}</b>\n" |
99 | 104 | response += f" • Requests: {user_data['total_requests']}\n" |
100 | 105 | response += f" • Audio Minutes: {user_data['total_minutes']:.1f}\n" |
101 | 106 | response += f" • Total Cost: ${user_data['total_cost']:.4f}\n" |
102 | | - |
| 107 | + |
103 | 108 | # Show breakdown of operations if available |
104 | 109 | if user_data["operations"]: |
105 | | - response += f" • Operations: " |
| 110 | + response += " • Operations: " |
106 | 111 | op_details = [] |
107 | 112 | for op, op_data in user_data["operations"].items(): |
108 | 113 | op_details.append(f"{op}({op_data['count']})") |
109 | 114 | response += ", ".join(op_details) + "\n" |
110 | | - |
| 115 | + |
111 | 116 | # Show activity timeframe |
112 | 117 | if user_data["first_activity"] and user_data["last_activity"]: |
113 | 118 | first = user_data["first_activity"] |
114 | 119 | last = user_data["last_activity"] |
115 | 120 | response += f" • Active: {first.strftime('%Y-%m-%d')} to {last.strftime('%Y-%m-%d')}\n" |
116 | | - |
| 121 | + |
117 | 122 | if len(user_stats) > 20: |
118 | 123 | response += f"\n<i>... and {len(user_stats) - 20} more users</i>" |
119 | | - |
| 124 | + |
120 | 125 | except Exception as e: |
121 | 126 | logger.error(f"Error getting statistics: {e}") |
122 | 127 | response = f"❌ Error retrieving statistics: {str(e)}" |
123 | | - |
| 128 | + |
124 | 129 | await send_safe(message.chat.id, response) |
125 | 130 |
|
126 | 131 |
|
@@ -274,15 +279,15 @@ async def ask_user_speedup(message: Message, app: App, state: FSMContext): |
274 | 279 | "none": "No speedup (original speed)", |
275 | 280 | "2": "2x speed (default)", |
276 | 281 | "3": "3x speed", |
277 | | - "4": "4x speed", |
| 282 | + "4": "4x speed", |
278 | 283 | "5": "5x speed", |
279 | 284 | }, |
280 | 285 | state=state, |
281 | 286 | default_choice="2", |
282 | 287 | timeout=10, |
283 | 288 | cleanup=app.config.cleanup_messages, |
284 | 289 | ) |
285 | | - |
| 290 | + |
286 | 291 | if speedup == "none": |
287 | 292 | return None |
288 | 293 | else: |
|
0 commit comments