88import asyncio
99import logging
1010from pathlib import Path
11- from typing import TYPE_CHECKING , Optional
11+ from typing import TYPE_CHECKING
1212
1313import typer
1414
1818from rich .console import Console
1919
2020from ..config .loader import load_config
21- from ..logging .db import TokenLogger
21+ from ..logging .db import TimePeriod , TokenLogger
2222from ..logging .stats import show_usage
2323from .auth_commands import app as auth_app
2424from .auth_commands import auto_login_if_needed as _auto_login_if_needed
@@ -48,6 +48,29 @@ def _build_token_store(cfg_path: Path | None = None):
4848 return cfg , store
4949
5050
51+ def _resolve_period (
52+ * ,
53+ days : int = 7 ,
54+ week : int | None = None ,
55+ month : int | None = None ,
56+ total : bool = False ,
57+ ) -> tuple [TimePeriod , int ]:
58+ """将互斥的时间维度标志解析为 (TimePeriod, count) 元组.
59+
60+ 优先级: ``-t`` > ``-m`` > ``-w`` > ``-d``。
61+
62+ Returns:
63+ ``(period, count)`` 元组。``count`` 表示查询最近第 N 个周期的数据。
64+ """
65+ if total :
66+ return TimePeriod .TOTAL , 1
67+ if month is not None :
68+ return TimePeriod .MONTH , max (1 , month )
69+ if week is not None :
70+ return TimePeriod .WEEK , max (1 , week )
71+ return TimePeriod .DAY , max (1 , days )
72+
73+
5174# ── 主命令 ─────────────────────────────────────────────────────
5275
5376
@@ -61,6 +84,7 @@ def start(
6184 import uvicorn
6285
6386 from ..server .app import create_app
87+ from .banner import print_banner
6488
6589 cfg_path = _resolve_config_path (config )
6690 cfg = load_config (cfg_path )
@@ -76,6 +100,10 @@ def start(
76100 from ..logging import build_log_config
77101
78102 fastapi_app = create_app (cfg )
103+
104+ # 打印启动品牌横幅
105+ print_banner (console , host = cfg .server .host , port = cfg .server .port )
106+
79107 uvicorn .run (
80108 fastapi_app ,
81109 host = cfg .server .host ,
@@ -113,29 +141,68 @@ def status(
113141
114142@app .command ()
115143def usage (
116- days : int = typer .Option (7 , "--days" , "-d" , help = "统计天数" ),
144+ days : int = typer .Option (7 , "--days" , "-d" , help = "统计天数(与 -w/-m/-t 互斥)" ),
145+ week : int | None = typer .Option (
146+ None , "--week" , "-w" , help = "最近第 N 周统计(按周聚合,默认 1)"
147+ ),
148+ month : int | None = typer .Option (
149+ None , "--month" , "-m" , help = "最近第 N 月统计(按月聚合,默认 1)"
150+ ),
151+ total : bool = typer .Option (False , "--total" , "-t" , help = "统计全部历史记录" ),
117152 vendor : str | None = typer .Option (None , "--vendor" , "-v" , help = "过滤供应商" ),
118- model : str | None = typer .Option (None , "--model" , "-m" , help = "过滤请求模型" ),
153+ model : str | None = typer .Option (
154+ None , "--model" , help = "过滤实际服务模型(model_served),逗号分隔可指定多个"
155+ ),
119156 db_path : str | None = typer .Option (None , "--db" , help = "数据库路径" ),
120157) -> None :
121- """查看 Token 使用统计."""
158+ """查看 Token 使用统计.
159+
160+ 时间维度(互斥,优先级 -t > -m > -w > -d):
161+
162+ \b
163+ -d 7 最近 7 天(默认,按日聚合)
164+ -w [N] 最近第 N 周(按周聚合,默认 1=本周)
165+ -m [N] 最近第 N 月(按月聚合,默认 1=本月)
166+ -t 全部历史(按供应商+模型聚合)
167+ """
168+ period , count = _resolve_period (days = days , week = week , month = month , total = total )
122169 cfg = load_config (Path (db_path ) if db_path else None )
123170 token_logger = TokenLogger (cfg .db_path )
124- asyncio .run (_run_usage (token_logger , days , vendor , model , cfg ))
171+ # 解析逗号分隔的多 vendor(如 "anthropic,zhipu" → ["anthropic", "zhipu"])
172+ vendor_filter : str | list [str ] | None = None
173+ if vendor :
174+ parts = [v .strip () for v in vendor .split ("," ) if v .strip ()]
175+ vendor_filter = parts [0 ] if len (parts ) == 1 else parts
176+ # 解析逗号分隔的多 model(如 "glm-5,glm-5.1" → ["glm-5", "glm-5.1"])
177+ model_filter : str | list [str ] | None = None
178+ if model :
179+ parts = [m .strip () for m in model .split ("," ) if m .strip ()]
180+ model_filter = parts [0 ] if len (parts ) == 1 else parts
181+ asyncio .run (
182+ _run_usage (token_logger , period , count , vendor_filter , model_filter , cfg )
183+ )
125184
126185
127186async def _run_usage (
128187 token_logger : TokenLogger ,
129- days : int ,
130- vendor : str | None ,
131- model : str | None ,
188+ period : TimePeriod ,
189+ count : int ,
190+ vendor : str | list [str ] | None ,
191+ model : str | list [str ] | None ,
132192 cfg : ProxyConfig ,
133193) -> None :
134194 from ..pricing import PricingTable
135195
136196 await token_logger .init ()
137197 pricing_table = PricingTable (cfg .pricing )
138- await show_usage (token_logger , days , vendor , model , pricing_table )
198+ await show_usage (
199+ token_logger ,
200+ vendor = vendor ,
201+ model = model ,
202+ pricing_table = pricing_table ,
203+ period = period ,
204+ count = count ,
205+ )
139206 await token_logger .close ()
140207
141208
0 commit comments