From 8e189ab3c553c8899cc66217015f5f534b620784 Mon Sep 17 00:00:00 2001 From: Rain-0x01-39 <83620631+Rain-0x01-39@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:54:40 +0800 Subject: [PATCH 01/11] =?UTF-8?q?fix(store):=20backfill=5Fcost=5Famounts?= =?UTF-8?q?=20=E5=9B=BA=E5=8C=96=E5=8E=9F=E5=A7=8B=E8=AE=A1=E8=B4=B9?= =?UTF-8?q?=E8=B4=A7=E5=B8=81=E8=80=8C=E9=9D=9E=E8=AF=AF=E6=A0=87=20USD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 用 compute_cost_with_currency 取 (金额, 货币代码),分别固化 cost_amount / currency_symbol。此前用 compute_cost_value 返回原始货币金额却一律标 "USD", 导致后续 _supplement_to_dict 回退路径把 CNY 数字当 USD 换算。 --- cost_control/store.py | 25 +++++++++++-------------- 1 file changed, 11 insertions(+), 14 deletions(-) diff --git a/cost_control/store.py b/cost_control/store.py index 3600c09..6101a3f 100644 --- a/cost_control/store.py +++ b/cost_control/store.py @@ -488,9 +488,9 @@ async def purge_module(self, module: str) -> int: async def backfill_cost_amounts(self, pricing: dict[str, Any]) -> int: """一次性回填 ``cost_amount IS NULL`` 的存量记录(幂等)。 - 逐行按 ``(provider_id, provider_model)`` 解析定价规则(历史定价无 currency - 字段,按 USD 口径算),把 USD 金额固化为 ``cost_amount``、 - ``currency_symbol="USD"``。失败行跳过(保持 NULL,展示时按主货币回退重算)。 + 逐行按 ``(provider_id, provider_model)`` 解析定价规则,把**原始计费货币** + 金额固化为 ``cost_amount``、``currency_symbol``(定价条目 ``currency`` 字段, + 缺省 ``"USD"``)。失败行跳过(保持 NULL,展示时按主货币回退重算)。 已有值的行不动。 Args: @@ -500,7 +500,7 @@ async def backfill_cost_amounts(self, pricing: dict[str, Any]) -> int: 成功回填的行数(失败返回 0)。 """ try: - from .cost import compute_cost_value + from .cost import compute_cost_with_currency maker = await self._ensure_session_maker() async with maker() as session: @@ -517,17 +517,14 @@ async def backfill_cost_amounts(self, pricing: dict[str, Any]) -> int: "token_output": int(getattr(r, "token_output", 0) or 0), "cache_creation": getattr(r, "cache_creation", None), } - cost_usd = round( - compute_cost_value( - usage, - getattr(r, "provider_id", "") or None, - getattr(r, "provider_model", None), - pricing, - ), - 6, + raw, cur = compute_cost_with_currency( + usage, + getattr(r, "provider_id", "") or None, + getattr(r, "provider_model", None), + pricing, ) - r.cost_amount = cost_usd # type: ignore[assignment] - r.currency_symbol = "USD" # type: ignore[assignment] + r.cost_amount = round(raw, 6) # type: ignore[assignment] + r.currency_symbol = cur or "USD" # type: ignore[assignment] n += 1 except Exception: continue From 1bb9a4fc0eae78cb530d4b00b7c58d5bf65683ff Mon Sep 17 00:00:00 2001 From: Rain-0x01-39 <83620631+Rain-0x01-39@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:56:20 +0800 Subject: [PATCH 02/11] =?UTF-8?q?fix(cost):=20query=5Fuser=5Fcost=5Ftotal?= =?UTF-8?q?=20=E6=8C=89=E4=B8=BB=E8=B4=A7=E5=B8=81=E5=8F=A3=E5=BE=84?= =?UTF-8?q?=E8=BF=94=E5=9B=9E=E5=B9=B6=E6=B6=88=E9=99=A4=E8=B0=83=E7=94=A8?= =?UTF-8?q?=E6=96=B9=E5=8F=8C=E9=87=8D=E6=8D=A2=E7=AE=97?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 此前函数内部按各行原始计费货币金额直接累加(混合货币和),三处调用方 (budget._override_used user 分支、check_budget per_user_daily、 web_api.api_budgets override user 分支)又把结果当 USD 二次换算到主货币, 构成双重错误。 改为:函数签名加 main_cur / rates,内部逐行用 compute_cost_with_currency 取 (金额, 货币) 后 convert 到主货币累加;三个调用方去掉二次换算直接传入 main_cur / rates。_override_used 的 user 分支此前根本没换算,一并修正。 --- cost_control/budget.py | 13 ++++++++----- cost_control/store.py | 41 ++++++++++++++++++++++++++--------------- cost_control/web_api.py | 9 ++++++--- 3 files changed, 40 insertions(+), 23 deletions(-) diff --git a/cost_control/budget.py b/cost_control/budget.py index 1dc22c0..8a199e1 100644 --- a/cost_control/budget.py +++ b/cost_control/budget.py @@ -364,7 +364,9 @@ async def _override_used( if tt == "user": if not user_id: return 0.0 - return float(await self.query_user_cost_total(tv, d_start, pricing)) + return round( + await self.query_user_cost_total(tv, d_start, pricing, main_cur, _rates), 6 + ) return 0.0 async def check_budget( @@ -567,11 +569,12 @@ async def check_budget( user_total_c = ses_cost if user_id and lc_user > 0: try: - # query_user_cost_total 返回 USD 口径,换算到主货币 - _uc = float( - await self.query_user_cost_total(user_id, d_start, pricing) + user_total_c = round( + await self.query_user_cost_total( + user_id, d_start, pricing, main_cur, rates + ), + 6, ) - user_total_c = round(_convert(_uc, "USD", main_cur, rates), 6) except Exception: user_total_c = ses_cost used_c_map = { diff --git a/cost_control/store.py b/cost_control/store.py index 6101a3f..0e1f792 100644 --- a/cost_control/store.py +++ b/cost_control/store.py @@ -327,19 +327,24 @@ async def query_user_cost_total( user_id: str, start: datetime, pricing: dict[str, Any], + main_cur: str = "USD", + rates: dict[str, float] | None = None, ) -> float: - """按 user_id 聚合自 ``start`` 以来的花费(supplement 路径,精确含 per_request)。 + """按 user_id 聚合自 ``start`` 以来的花费,换算到 ``main_cur``(主货币口径)。 - 逐行按 (provider_id, model) 解析定价规则: + 逐行按 (provider_id, model) 解析定价规则,取**原始计费货币**金额后经汇率 + 换算到 ``main_cur`` 累加: - - per_token / per_turn:逐行算后求和。 + - per_token / per_turn:逐行算后换算求和。 - per_request:按 provider 聚合 distinct ``request_id`` 数 × price(**精确**, - supplement 表有 request_id;主表路径无此字段只能近似)。request_id 为 NULL - 的行无法归属,跳过。 + supplement 表有 request_id;主表路径无此字段只能近似),再换算到主货币。 + request_id 为 NULL 的行无法归属,跳过。 """ try: - from .cost import _cost_per_token, resolve_pricing + from .cost import compute_cost_with_currency, resolve_pricing + from .exchange_rates import convert as _convert + _rates = rates if rates else {} maker = await self._ensure_session_maker() async with maker() as session: stmt = select(CostSupplement).where(CostSupplement.user_id == user_id) @@ -349,8 +354,8 @@ async def query_user_cost_total( rows = list(result.scalars().all()) total = 0.0 - # per_request:按 provider 聚合 distinct request_id(精确) - req_prices: dict[str, float] = {} + # per_request:按 provider 聚合 (price, 货币代码) + req_prices: dict[str, tuple[float, str]] = {} for r in rows: try: provider_id = getattr(r, "provider_id", "") or None @@ -358,26 +363,31 @@ async def query_user_cost_total( rule = resolve_pricing(provider_id, model, pricing) if rule is None: continue + cur = str(rule.get("currency", "USD") or "USD").strip().upper() or "USD" mode = rule.get("mode", "per_token") if mode == "per_token": - total += _cost_per_token( + raw, _cur = compute_cost_with_currency( { "token_input_other": int(getattr(r, "token_input_other", 0) or 0), "token_input_cached": int(getattr(r, "token_input_cached", 0) or 0), "token_output": int(getattr(r, "token_output", 0) or 0), "cache_creation": getattr(r, "cache_creation", None), }, - rule, + provider_id, + model, + pricing, ) + total += _convert(raw, _cur, main_cur, _rates) elif mode == "per_turn": - total += float(rule.get("price", 0.0) or 0.0) + raw = float(rule.get("price", 0.0) or 0.0) + total += _convert(raw, cur, main_cur, _rates) elif mode == "per_request": pid = provider_id or "" - req_prices[pid] = float(rule.get("price", 0.0) or 0.0) + req_prices[pid] = (float(rule.get("price", 0.0) or 0.0), cur) except Exception: continue - # per_request 精确:每个 provider 的 distinct request_id 数 × price + # per_request 精确:每个 provider 的 distinct request_id 数 × price,再换算到主货币 if req_prices: distinct: dict[str, set[str]] = {} for r in rows: @@ -387,8 +397,9 @@ async def query_user_cost_total( rid = getattr(r, "request_id", None) if rid: distinct.setdefault(pid, set()).add(str(rid)) - for pid, price in req_prices.items(): - total += len(distinct.get(pid, set())) * price + for pid, (price, cur) in req_prices.items(): + cnt = len(distinct.get(pid, set())) + total += _convert(cnt * price, cur, main_cur, _rates) return round(total, 6) except Exception as e: logger.warning("[cost_control] query_user_cost_total 失败: %s", e) diff --git a/cost_control/web_api.py b/cost_control/web_api.py index 4b2b853..91d772a 100644 --- a/cost_control/web_api.py +++ b/cost_control/web_api.py @@ -1080,9 +1080,12 @@ def _dim_entry( if ov.get("token_limit", 0) > 0 and hasattr(self, "query_user_token_total"): used_t_v = float(await self.query_user_token_total(tv, d_start)) if ov.get("cost_limit", 0) > 0 and hasattr(self, "query_user_cost_total"): - # query_user_cost_total 返回 USD 口径,换算到主货币 - _uc = float(await self.query_user_cost_total(tv, d_start, pricing)) - used_c_v = round(_conv(_uc, "USD", main_cur, rates), 6) + used_c_v = round( + await self.query_user_cost_total( + tv, d_start, pricing, main_cur, rates + ), + 6, + ) except Exception: # 单条 override 聚合失败不影响其它条 pass From fdd8fb0883e09448bba7d0d6404664f805a11a06 Mon Sep 17 00:00:00 2001 From: Rain-0x01-39 <83620631+Rain-0x01-39@users.noreply.github.com> Date: Wed, 26 Aug 2026 11:57:43 +0800 Subject: [PATCH 03/11] =?UTF-8?q?fix(commands):=20/cost=20=E7=94=A8?= =?UTF-8?q?=E4=B8=BB=E8=B4=A7=E5=B8=81=E5=8F=A3=E5=BE=84=E8=81=9A=E5=90=88?= =?UTF-8?q?=E6=88=90=E6=9C=AC=E8=80=8C=E9=9D=9E=E6=B7=B7=E5=90=88=E8=B4=A7?= =?UTF-8?q?=E5=B8=81=E7=9B=B4=E6=8E=A5=E7=9B=B8=E5=8A=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit cmd_cost 此前用 compute_row_cost 把各行原始计费货币金额直接相加(存在 非 USD 定价条目时为混合货币和),却用主货币符号显示。改用 compute_row_cost_in_main 逐行换算到主货币后求和,与 schedule.py 日报、 budget.check_budget 口径一致。 --- cost_control/commands.py | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/cost_control/commands.py b/cost_control/commands.py index 4ff1f78..d1c35e9 100644 --- a/cost_control/commands.py +++ b/cost_control/commands.py @@ -23,8 +23,8 @@ from .attributor import ESTIMATION_NOTE from .budget import _DIM_ORDER, day_window_start, resolve_tz from .config import get_config -from .cost import compute_row_cost -from .exchange_rates import currency_to_symbol, get_main_currency +from .cost import compute_row_cost_in_main +from .exchange_rates import currency_to_symbol, get_main_currency, get_rates # 插件主模块路径(``main.py``)。AstrBot 的 ``star_map`` 以 ``Main.__module__`` # 为键,而 ``update_command_permission`` 等管理接口通过 ``handler.__module__`` @@ -85,8 +85,10 @@ async def cmd_cost(self, event: AstrMessageEvent): usage = await self.query_usage(umo=umo, start=d_start) rows = await self.query_usage_grouped(by="provider_model", umo=umo, start=d_start) pricing = self.get_pricing() - sym = currency_to_symbol(get_main_currency(getattr(self, "cfg", None))) - cost = round(sum(compute_row_cost(r, pricing) for r in rows), 6) + main_cur = get_main_currency(getattr(self, "cfg", None)) + rates = get_rates(getattr(self, "cfg", None)) + sym = currency_to_symbol(main_cur) + cost = round(sum(compute_row_cost_in_main(r, pricing, main_cur, rates) for r in rows), 6) lines = [ "💰 今日用量(本会话)", f"调用 {usage.get('count', 0)} 次,成本 ≈ {sym}{cost:.4f}", @@ -95,7 +97,7 @@ async def cmd_cost(self, event: AstrMessageEvent): f"输出 {usage.get('token_output', 0)}", ] for r in rows[:5]: - c = round(compute_row_cost(r, pricing), 6) + c = round(compute_row_cost_in_main(r, pricing, main_cur, rates), 6) name = r.get("provider_model") or r.get("key") or "?" lines.append(f" · {name}:{r.get('count', 0)}次 / {sym}{c:.4f}") yield event.plain_result("\n".join(lines)) From ea3360a699fdb3da01d9e06019eb227286413660 Mon Sep 17 00:00:00 2001 From: Rain-0x01-39 <83620631+Rain-0x01-39@users.noreply.github.com> Date: Wed, 26 Aug 2026 12:33:06 +0800 Subject: [PATCH 04/11] =?UTF-8?q?fix(web=5Fapi):=20api=5Fbudgets=20?= =?UTF-8?q?=E9=99=90=E9=A2=9D=E6=8D=A2=E7=AE=97=E5=88=B0=E4=B8=BB=E8=B4=A7?= =?UTF-8?q?=E5=B8=81=E5=8F=A3=E5=BE=84=EF=BC=8C=E6=B6=88=E9=99=A4=20ratio/?= =?UTF-8?q?exceeded=20=E9=94=99=E5=88=A4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit S3: 全局 5 维 cost 限额(limits_cost)按 budgets_cost_currency 各自填的 原始货币值,而 used 已是主货币口径,_part 算 ratio/exceeded 时口径错配。 参照 budget.check_budget:426-435 换算到主货币得到 limits_cost_main, _dim_entry 用它比较;响应保留 limits_cost 原始值供前端编辑框初始值, 新增 limits_cost_main 供展示/判定。 S4: override 的 cost_limit 按 cost_currency 原始货币填,_ratio 直接拿来 与主货币口径的 used_c_v 比较。参照 check_budget:494-497 先换算到主货币 再比较;cost_limit / cost_currency 保留原始值供编辑。 --- cost_control/web_api.py | 33 +++++++++++++++++++++++++++++---- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/cost_control/web_api.py b/cost_control/web_api.py index 91d772a..1a628dd 100644 --- a/cost_control/web_api.py +++ b/cost_control/web_api.py @@ -1008,6 +1008,22 @@ async def api_budgets(self, **kwargs: Any) -> dict[str, Any]: else: day_cost = month_cost = ses_cost = mod_cost = 0.0 + # 全局 cost 限额可能带每维度货币(budgets_cost_currency),统一换算到主货币。 + # limits_cost 保留原始值供前端编辑框初始值,limits_cost_main 供展示/判定。 + from .config import get_budgets_cost_currency + from .exchange_rates import convert as _conv + + _bcc = get_budgets_cost_currency(cfg) + limits_cost_main: dict[str, float] = {} + for _d in _DIM_ORDER: + _lc_raw = float(limits_cost.get(_d, 0) or 0) + _d_cur = str(_bcc.get(_d, "") or "") or main_cur + limits_cost_main[_d] = ( + round(_conv(_lc_raw, _d_cur, main_cur, rates), 6) + if _lc_raw > 0 and _d_cur != main_cur + else _lc_raw + ) + def _part(limit: Any, used: Any) -> dict[str, Any]: limit = float(limit or 0) used = float(used or 0) @@ -1028,7 +1044,7 @@ def _dim_entry( "note": note, }, "cost": { - **_part(limits_cost.get(key, 0), used_c), + **_part(limits_cost_main.get(key, 0), used_c), "top_key": top_key, "note": note, }, @@ -1039,7 +1055,6 @@ def _dim_entry( pricing = self.get_pricing() if has_cost or overrides_raw else {} if has_cost or overrides_raw: from .cost import compute_cost_grouped_in_main - from .exchange_rates import convert as _conv overrides_out: list[dict[str, Any]] = [] for idx, ov in enumerate(overrides_raw): @@ -1098,6 +1113,15 @@ def _ratio(used: float, limit: Any) -> dict[str, Any]: "exceeded": limit_f > 0 and used >= limit_f, } + # override 的 cost_limit 可能带 cost_currency,先换算到主货币再比较/展示。 + # cost_limit / cost_currency 保留原始值供前端编辑框;current.cost 用主货币口径。 + _ov_lc = float(ov.get("cost_limit") or 0.0) + _ov_cur = str(ov.get("cost_currency") or "") or main_cur + _ov_lc_main = ( + round(_conv(_ov_lc, _ov_cur, main_cur, rates), 6) + if _ov_lc > 0 and _ov_cur != main_cur + else _ov_lc + ) overrides_out.append( { "id": f"ovo_{idx}", @@ -1113,7 +1137,7 @@ def _ratio(used: float, limit: Any) -> dict[str, Any]: "fallback_token_limit": int(ov.get("fallback_token_limit") or 0), "current": { "token": _ratio(used_t_v, ov.get("token_limit", 0)), - "cost": _ratio(used_c_v, ov.get("cost_limit", 0)), + "cost": _ratio(used_c_v, _ov_lc_main), }, } ) @@ -1121,13 +1145,14 @@ def _ratio(used: float, limit: Any) -> dict[str, Any]: fallback_providers = get_fallback_providers(cfg) # 各维度 cost 限额的独立货币(budgets_cost_currency) - from .config import get_budgets_cost_currency, get_currency_symbol + from .config import get_currency_symbol from .exchange_rates import get_rate_updated_at return self._ok( { "limits": limits, "limits_cost": limits_cost, + "limits_cost_main": limits_cost_main, "limits_cost_currency": get_budgets_cost_currency(cfg), "currency_symbol": get_currency_symbol(cfg), "exchange_rates": rates, From 59267b5301c3baa8d7c4d9186d3a421766356e9f Mon Sep 17 00:00:00 2001 From: Rain-0x01-39 <83620631+Rain-0x01-39@users.noreply.github.com> Date: Wed, 26 Aug 2026 21:19:20 +0800 Subject: [PATCH 05/11] =?UTF-8?q?fix(store):=20=E8=BF=81=E7=A7=BB=E4=BF=AE?= =?UTF-8?q?=E6=AD=A3=E6=97=A7=E7=89=88=20backfill=20=E8=AF=AF=E6=A0=87=20U?= =?UTF-8?q?SD=20=E7=9A=84=E5=8E=86=E5=8F=B2=E8=A1=8C=E8=B4=A7=E5=B8=81?= =?UTF-8?q?=E6=A0=87=E8=AE=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 旧版 backfill_cost_amounts 用 compute_cost_value 固化的金额是原始计费货币, 却一律标 currency_symbol="USD",导致展示层把 CNY 数字当 USD 换算。 新增 fix_mislabeled_cost_currency:仅修正 currency_symbol 为定价条目实际 货币(不动 cost_amount,保持历史快照),main.py initialize 时执行。 --- cost_control/store.py | 48 +++++++++++++++++++++++++++++++++++++++++++ main.py | 7 +++++++ 2 files changed, 55 insertions(+) diff --git a/cost_control/store.py b/cost_control/store.py index 0e1f792..ce6af5d 100644 --- a/cost_control/store.py +++ b/cost_control/store.py @@ -545,6 +545,54 @@ async def backfill_cost_amounts(self, pricing: dict[str, Any]) -> int: logger.warning("[cost_control] backfill_cost_amounts 失败: %s", e) return 0 + async def fix_mislabeled_cost_currency(self, pricing: dict[str, Any]) -> int: + """一次性迁移:修正旧版 backfill 误标 USD 的历史行(幂等)。 + + 旧版 ``backfill_cost_amounts`` 用 :func:`compute_cost_value` 固化的金额是 + **原始计费货币**,却一律标 ``currency_symbol="USD"``。本迁移只修正 + ``currency_symbol`` 为定价条目的实际货币,**不动** ``cost_amount``(保持 + 历史快照)。仅处理 ``cost_amount IS NOT NULL AND currency_symbol == "USD"`` + 的行,避免覆盖已正确固化的数据。 + + Args: + pricing: :func:`get_pricing` 返回的 ``{"defaults", "user"}`` 结构。 + + Returns: + 修正货币标记的行数(失败返回 0)。 + """ + try: + from .cost import resolve_pricing + + maker = await self._ensure_session_maker() + async with maker() as session: + stmt = select(CostSupplement).where( + CostSupplement.cost_amount.is_not(None), # type: ignore[union-attr] + CostSupplement.currency_symbol == "USD", # type: ignore[union-attr] + ) + result = await session.execute(stmt) + rows = list(result.scalars().all()) + n = 0 + for r in rows: + try: + rule = resolve_pricing( + getattr(r, "provider_id", "") or None, + getattr(r, "provider_model", None), + pricing, + ) + if rule is None: + continue + cur = str(rule.get("currency", "USD") or "USD").strip().upper() or "USD" + if cur != "USD": + r.currency_symbol = cur # type: ignore[assignment] + n += 1 + except Exception: + continue + await session.commit() + return n + except Exception as e: + logger.warning("[cost_control] fix_mislabeled_cost_currency 失败: %s", e) + return 0 + async def save_cache_event(self, record: dict[str, Any]) -> None: """保存一条缓存诊断事件(``run_cache_diag`` 检测到破坏时调用)。""" row = CacheEvent( diff --git a/main.py b/main.py index 6a620ff..ad346ad 100644 --- a/main.py +++ b/main.py @@ -105,6 +105,13 @@ async def initialize(self) -> None: logger.info("[cost_control] 已为 %d 条历史记录补算 cost_amount", n) except Exception as e: logger.warning("[cost_control] 历史记录补算失败(不影响运行): %s", e) + # 一次性迁移:修正旧版 backfill 误标 USD 的历史行 + try: + n = await self.fix_mislabeled_cost_currency(self.get_pricing()) + if n > 0: + logger.info("[cost_control] 已修正 %d 条历史记录的货币标记", n) + except Exception as e: + logger.warning("[cost_control] 历史货币标记修正失败(不影响运行): %s", e) try: await self.register_cron() except Exception as e: From 64c0475f1bcc2ee918399dfae8e1e9afd67d9616 Mon Sep 17 00:00:00 2001 From: Rain-0x01-39 <83620631+Rain-0x01-39@users.noreply.github.com> Date: Wed, 26 Aug 2026 21:19:21 +0800 Subject: [PATCH 06/11] =?UTF-8?q?fix(budget):=20override=20=E8=B6=85?= =?UTF-8?q?=E9=99=90=E8=BF=94=E5=9B=9E=20currency=20=E6=94=B9=E4=B8=BA?= =?UTF-8?q?=E4=B8=BB=E8=B4=A7=E5=B8=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 比较时 used/limit 均已换算到主货币,返回结果却标 ov_cur,_format_message 按限额原币种符号显示主货币金额(如主货币 CNY 时显示 $80 / $72)。改为 返回 main_cur。 --- cost_control/budget.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cost_control/budget.py b/cost_control/budget.py index ff90daa..cde19a4 100644 --- a/cost_control/budget.py +++ b/cost_control/budget.py @@ -502,7 +502,7 @@ async def check_budget( "metric": "cost", "limit": lc_main, "used": used_c, - "currency": ov_cur, + "currency": main_cur, "on_exceeded": str(ov.get("on_exceeded") or "stop"), "fallback_provider_ids": list( ov.get("fallback_provider_ids") or [] From f3ebf97e1e884a44c0cbd7442ead1cb21366053b Mon Sep 17 00:00:00 2001 From: Rain-0x01-39 <83620631+Rain-0x01-39@users.noreply.github.com> Date: Wed, 26 Aug 2026 21:19:21 +0800 Subject: [PATCH 07/11] =?UTF-8?q?fix(web=5Fapi):=20=5Fsupplement=5Fto=5Fdi?= =?UTF-8?q?ct=20=E6=97=A0=E5=9B=BA=E5=8C=96=E9=87=91=E9=A2=9D=E5=9B=9E?= =?UTF-8?q?=E9=80=80=E8=B7=AF=E5=BE=84=E6=8D=A2=E7=AE=97=E5=88=B0=E4=B8=BB?= =?UTF-8?q?=E8=B4=A7=E5=B8=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 回退调用 compute_cost_value 返回原始计费货币金额,却直接作为主货币 cost 输出(主货币 USD 时 CNY 7.2 的记录显示 7.2 而非 1.0)。改用 compute_cost_in_main 即时换算到主货币,与 analytics 口径一致。 --- cost_control/web_api.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/cost_control/web_api.py b/cost_control/web_api.py index 0bf55e7..c293bd6 100644 --- a/cost_control/web_api.py +++ b/cost_control/web_api.py @@ -295,7 +295,7 @@ def _supplement_to_dict( 从固化的原始货币换算到主货币 ``main_cur``,``cost_original`` 保留原始金额; 否则 ``cost`` 由 pricing 即时算出(回退,用于历史未回填行)。 """ - from .cost import compute_cost_value + from .cost import compute_cost_in_main from .exchange_rates import convert created = getattr(s, "created_at", None) @@ -315,9 +315,9 @@ def _supplement_to_dict( cur = str(currency_symbol or "USD") cost = round(convert(cost_original, cur, main_cur, rates or {}), 6) elif pricing is not None: - # 回退:无固化金额,按定价即时算(USD 口径) + # 回退:无固化金额,按定价即时算并换算到主货币 cost = round( - compute_cost_value( + compute_cost_in_main( { "token_input_other": token_input_other, "token_input_cached": token_input_cached, @@ -327,6 +327,8 @@ def _supplement_to_dict( getattr(s, "provider_id", None) or None, getattr(s, "provider_model", None), pricing, + main_cur, + rates or {}, ), 6, ) @@ -1105,6 +1107,7 @@ def _dim_entry( def _ratio(used: float, limit: Any) -> dict[str, Any]: limit_f = float(limit or 0) return { + "limit": round(limit_f, 6), "used": round(used, 6), "ratio": round(used * 100.0 / limit_f, 1) if limit_f > 0 else 0.0, "exceeded": limit_f > 0 and used >= limit_f, From 17e5be75348daf26323fa18b82b0615625c62cbb Mon Sep 17 00:00:00 2001 From: Rain-0x01-39 <83620631+Rain-0x01-39@users.noreply.github.com> Date: Wed, 26 Aug 2026 21:19:33 +0800 Subject: [PATCH 08/11] =?UTF-8?q?fix(frontend):=20=E9=A2=84=E7=AE=97?= =?UTF-8?q?=E9=A1=B5=E5=B1=95=E7=A4=BA=E7=BB=9F=E4=B8=80=E4=B8=BB=E8=B4=A7?= =?UTF-8?q?=E5=B8=81=E5=8F=A3=E5=BE=84=E5=B9=B6=E6=B6=88=E8=B4=B9=E6=8D=A2?= =?UTF-8?q?=E7=AE=97=E5=90=8E=E9=99=90=E9=A2=9D?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 后端 /budgets 的 dimensions.cost / override current.cost 已是主货币金额, 前端此前仍按限额原币种符号展示原始 limits_cost / cost_limit,导致 主货币 CNY、限额 USD 10 时界面显示 $36 / $10。改为: - GlobalDefaultsPanel 展示行用 dimensions.cost 的 used/limit(主货币值) + 缺省主货币符号;编辑框保留原始货币输入语义。 - OverrideRow 状态行用 current.cost.used/limit + 主货币符号。 - 后端 _ratio 补充 limit 字段(主货币口径),类型 OverrideCurrent.cost 增加 limit;BudgetResponse 增加 limits_cost_main 字段。 - 同步更新构建产物 pages/dashboard(index.html / style.css / app.js)。 --- .../src/components/GlobalDefaultsPanel.tsx | 3 +- frontend/src/components/OverrideRow.tsx | 4 +-- frontend/src/lib/types.ts | 3 +- frontend/src/views/BudgetsView.tsx | 2 +- pages/dashboard/app.js | 2 +- pages/dashboard/index.html | 30 +++++++++---------- 6 files changed, 22 insertions(+), 22 deletions(-) diff --git a/frontend/src/components/GlobalDefaultsPanel.tsx b/frontend/src/components/GlobalDefaultsPanel.tsx index df18400..6c017a3 100644 --- a/frontend/src/components/GlobalDefaultsPanel.tsx +++ b/frontend/src/components/GlobalDefaultsPanel.tsx @@ -105,8 +105,7 @@ export function GlobalDefaultsPanel({ ))}
- {fmtCost(c.used, budgetsCostCurrency[d.key] || "")} /{" "} - {fmtCost(limitsCost[d.key] || 0, budgetsCostCurrency[d.key] || "")} + {fmtCost(c.used)} / {fmtCost(c.limit)}
{c.limit > 0 ? ( {c.ratio || 0}% diff --git a/frontend/src/components/OverrideRow.tsx b/frontend/src/components/OverrideRow.tsx index 1731b09..546c890 100644 --- a/frontend/src/components/OverrideRow.tsx +++ b/frontend/src/components/OverrideRow.tsx @@ -200,11 +200,11 @@ export function OverrideRow({ )} diff --git a/frontend/src/lib/types.ts b/frontend/src/lib/types.ts index 532cd1b..03240e8 100644 --- a/frontend/src/lib/types.ts +++ b/frontend/src/lib/types.ts @@ -161,7 +161,7 @@ export type OnExceeded = "stop" | "fallback" | "warn"; export interface OverrideCurrent { token: { used: number; ratio: number; exceeded: boolean }; - cost: { used: number; ratio: number; exceeded: boolean }; + cost: { used: number; limit: number; ratio: number; exceeded: boolean }; } export interface BudgetOverride { @@ -191,6 +191,7 @@ export interface FallbackProvider { export interface BudgetResponse { limits?: Record; limits_cost?: Record; + limits_cost_main?: Record; limits_cost_currency?: Record; currency_symbol?: string; exchange_rates?: Record; diff --git a/frontend/src/views/BudgetsView.tsx b/frontend/src/views/BudgetsView.tsx index 7256c5f..823c59f 100644 --- a/frontend/src/views/BudgetsView.tsx +++ b/frontend/src/views/BudgetsView.tsx @@ -35,7 +35,7 @@ function emptyOverride(targetType: OverrideTarget = "umo"): BudgetOverrideRow { stop_message: "", fallback_provider_ids: [], fallback_token_limit: 0, - current: { token: { used: 0, ratio: 0, exceeded: false }, cost: { used: 0, ratio: 0, exceeded: false } }, + current: { token: { used: 0, ratio: 0, exceeded: false }, cost: { used: 0, limit: 0, ratio: 0, exceeded: false } }, }; } diff --git a/pages/dashboard/app.js b/pages/dashboard/app.js index 8fe62ec..9e0decb 100644 --- a/pages/dashboard/app.js +++ b/pages/dashboard/app.js @@ -105,5 +105,5 @@ In order to be iterable, non-array objects must have a [Symbol.iterator]() metho In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function hG(e,t){if(e){if(typeof e=="string")return Ch(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);if(r==="Object"&&e.constructor&&(r=e.constructor.name),r==="Map"||r==="Set")return Array.from(e);if(r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return Ch(e,t)}}function vG(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}function mG(e){if(Array.isArray(e))return Ch(e)}function Ch(e,t){(t==null||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);rc){h=[].concat(Pi(s.slice(0,v)),[c-g]);break}var w=h.length%2===0?[0,d]:[d];return[].concat(Pi(t.repeat(s,f)),Pi(h),w).map(function(y){return"".concat(y,"px")}).join(", ")}),lr(r,"id",za("recharts-line-")),lr(r,"pathRef",function(o){r.mainCurve=o}),lr(r,"handleAnimationEnd",function(){r.setState({isAnimationFinished:!0}),r.props.onAnimationEnd&&r.props.onAnimationEnd()}),lr(r,"handleAnimationStart",function(){r.setState({isAnimationFinished:!1}),r.props.onAnimationStart&&r.props.onAnimationStart()}),r}return SG(t,e),gG(t,[{key:"componentDidMount",value:function(){if(this.props.isAnimationActive){var n=this.getTotalLength();this.setState({totalLength:n})}}},{key:"componentDidUpdate",value:function(){if(this.props.isAnimationActive){var n=this.getTotalLength();n!==this.state.totalLength&&this.setState({totalLength:n})}}},{key:"getTotalLength",value:function(){var n=this.mainCurve;try{return n&&n.getTotalLength&&n.getTotalLength()||0}catch{return 0}}},{key:"renderErrorBar",value:function(n,i){if(this.props.isAnimationActive&&!this.state.isAnimationFinished)return null;var a=this.props,o=a.points,l=a.xAxis,s=a.yAxis,u=a.layout,f=a.children,c=Yt(f,Gl);if(!c)return null;var d=function(g,w){return{x:g.x,y:g.y,value:g.value,errorVal:yt(g.payload,w)}},h={clipPath:n?"url(#clipPath-".concat(i,")"):null};return E.createElement(Oe,h,c.map(function(v){return E.cloneElement(v,{key:"bar-".concat(v.props.dataKey),data:o,xAxis:l,yAxis:s,layout:u,dataPointFormatter:d})}))}},{key:"renderDots",value:function(n,i,a){var o=this.props.isAnimationActive;if(o&&!this.state.isAnimationFinished)return null;var l=this.props,s=l.dot,u=l.points,f=l.dataKey,c=ae(this.props,!1),d=ae(s,!0),h=u.map(function(g,w){var y=kt(kt(kt({key:"dot-".concat(w),r:3},c),d),{},{index:w,cx:g.x,cy:g.y,value:g.value,dataKey:f,payload:g.payload,points:u});return t.renderDotItem(s,y)}),v={clipPath:n?"url(#clipPath-".concat(i?"":"dots-").concat(a,")"):null};return E.createElement(Oe,Mo({className:"recharts-line-dots",key:"dots"},v),h)}},{key:"renderCurveStatically",value:function(n,i,a,o){var l=this.props,s=l.type,u=l.layout,f=l.connectNulls;l.ref;var c=Hx(l,cG),d=kt(kt(kt({},ae(c,!0)),{},{fill:"none",className:"recharts-line-curve",clipPath:i?"url(#clipPath-".concat(a,")"):null,points:n},o),{},{type:s,layout:u,connectNulls:f});return E.createElement(Zi,Mo({},d,{pathRef:this.pathRef}))}},{key:"renderCurveWithAnimation",value:function(n,i){var a=this,o=this.props,l=o.points,s=o.strokeDasharray,u=o.isAnimationActive,f=o.animationBegin,c=o.animationDuration,d=o.animationEasing,h=o.animationId,v=o.animateNewValues,g=o.width,w=o.height,y=this.state,m=y.prevPoints,x=y.totalLength;return E.createElement(Er,{begin:f,duration:c,isActive:u,easing:d,from:{t:0},to:{t:1},key:"line-".concat(h),onAnimationEnd:this.handleAnimationEnd,onAnimationStart:this.handleAnimationStart},function(S){var b=S.t;if(m){var _=m.length/l.length,O=l.map(function(k,R){var C=Math.floor(R*_);if(m[C]){var D=m[C],U=rt(D.x,k.x),W=rt(D.y,k.y);return kt(kt({},k),{},{x:U(b),y:W(b)})}if(v){var $=rt(g*2,k.x),L=rt(w/2,k.y);return kt(kt({},k),{},{x:$(b),y:L(b)})}return kt(kt({},k),{},{x:k.x,y:k.y})});return a.renderCurveStatically(O,n,i)}var j=rt(0,x),P=j(b),T;if(s){var N="".concat(s).split(/[,\s]+/gim).map(function(k){return parseFloat(k)});T=a.getStrokeDasharray(P,x,N)}else T=a.generateSimpleStrokeDasharray(x,P);return a.renderCurveStatically(l,n,i,{strokeDasharray:T})})}},{key:"renderCurve",value:function(n,i){var a=this.props,o=a.points,l=a.isAnimationActive,s=this.state,u=s.prevPoints,f=s.totalLength;return l&&o&&o.length&&(!u&&f>0||!ha(u,o))?this.renderCurveWithAnimation(n,i):this.renderCurveStatically(o,n,i)}},{key:"render",value:function(){var n,i=this.props,a=i.hide,o=i.dot,l=i.points,s=i.className,u=i.xAxis,f=i.yAxis,c=i.top,d=i.left,h=i.width,v=i.height,g=i.isAnimationActive,w=i.id;if(a||!l||!l.length)return null;var y=this.state.isAnimationFinished,m=l.length===1,x=ue("recharts-line",s),S=u&&u.allowDataOverflow,b=f&&f.allowDataOverflow,_=S||b,O=oe(w)?this.id:w,j=(n=ae(o,!1))!==null&&n!==void 0?n:{r:3,strokeWidth:2},P=j.r,T=P===void 0?3:P,N=j.strokeWidth,k=N===void 0?2:N,R=zS(o)?o:{},C=R.clipDot,D=C===void 0?!0:C,U=T*2+k;return E.createElement(Oe,{className:x},S||b?E.createElement("defs",null,E.createElement("clipPath",{id:"clipPath-".concat(O)},E.createElement("rect",{x:S?d:d-h/2,y:b?c:c-v/2,width:S?h:h*2,height:b?v:v*2})),!D&&E.createElement("clipPath",{id:"clipPath-dots-".concat(O)},E.createElement("rect",{x:d-U/2,y:c-U/2,width:h+U,height:v+U}))):null,!m&&this.renderCurve(_,O),this.renderErrorBar(_,O),(m||o)&&this.renderDots(_,D,O),(!g||y)&&Wr.renderCallByParent(this.props,l))}}],[{key:"getDerivedStateFromProps",value:function(n,i){return n.animationId!==i.prevAnimationId?{prevAnimationId:n.animationId,curPoints:n.points,prevPoints:i.curPoints}:n.points!==i.curPoints?{curPoints:n.points}:null}},{key:"repeat",value:function(n,i){for(var a=n.length%2!==0?[].concat(Pi(n),[0]):n,o=[],l=0;l=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}function PG(e,t){if(e==null)return{};var r={};for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n)){if(t.indexOf(n)>=0)continue;r[n]=e[n]}return r}function ei(){return ei=Object.assign?Object.assign.bind():function(e){for(var t=1;t0||!ha(f,o)||!ha(c,l))?this.renderAreaWithAnimation(n,i):this.renderAreaStatically(o,l,n,i)}},{key:"render",value:function(){var n,i=this.props,a=i.hide,o=i.dot,l=i.points,s=i.className,u=i.top,f=i.left,c=i.xAxis,d=i.yAxis,h=i.width,v=i.height,g=i.isAnimationActive,w=i.id;if(a||!l||!l.length)return null;var y=this.state.isAnimationFinished,m=l.length===1,x=ue("recharts-area",s),S=c&&c.allowDataOverflow,b=d&&d.allowDataOverflow,_=S||b,O=oe(w)?this.id:w,j=(n=ae(o,!1))!==null&&n!==void 0?n:{r:3,strokeWidth:2},P=j.r,T=P===void 0?3:P,N=j.strokeWidth,k=N===void 0?2:N,R=zS(o)?o:{},C=R.clipDot,D=C===void 0?!0:C,U=T*2+k;return E.createElement(Oe,{className:x},S||b?E.createElement("defs",null,E.createElement("clipPath",{id:"clipPath-".concat(O)},E.createElement("rect",{x:S?f:f-h/2,y:b?u:u-v/2,width:S?h:h*2,height:b?v:v*2})),!D&&E.createElement("clipPath",{id:"clipPath-dots-".concat(O)},E.createElement("rect",{x:f-U/2,y:u-U/2,width:h+U,height:v+U}))):null,m?null:this.renderArea(_,O),(o||m)&&this.renderDots(_,D,O),(!g||y)&&Wr.renderCallByParent(this.props,l))}}],[{key:"getDerivedStateFromProps",value:function(n,i){return n.animationId!==i.prevAnimationId?{prevAnimationId:n.animationId,curPoints:n.points,curBaseLine:n.baseLine,prevPoints:i.curPoints,prevBaseLine:i.curBaseLine}:n.points!==i.curPoints||n.baseLine!==i.curBaseLine?{curPoints:n.points,curBaseLine:n.baseLine}:null}}])}(A.PureComponent);zj=Ln;wr(Ln,"displayName","Area");wr(Ln,"defaultProps",{stroke:"#3182bd",fill:"#3182bd",fillOpacity:.6,xAxisId:0,yAxisId:0,legendType:"line",connectNulls:!1,points:[],dot:!1,activeDot:!0,hide:!1,isAnimationActive:!gi.isSsr,animationBegin:0,animationDuration:1500,animationEasing:"ease"});wr(Ln,"getBaseValue",function(e,t,r,n){var i=e.layout,a=e.baseValue,o=t.props.baseValue,l=o??a;if(K(l)&&typeof l=="number")return l;var s=i==="horizontal"?n:r,u=s.scale.domain();if(s.type==="number"){var f=Math.max(u[0],u[1]),c=Math.min(u[0],u[1]);return l==="dataMin"?c:l==="dataMax"||f<0?f:Math.max(Math.min(u[0],u[1]),0)}return l==="dataMin"?u[0]:l==="dataMax"?u[1]:u[0]});wr(Ln,"getComposedData",function(e){var t=e.props,r=e.item,n=e.xAxis,i=e.yAxis,a=e.xAxisTicks,o=e.yAxisTicks,l=e.bandSize,s=e.dataKey,u=e.stackedData,f=e.dataStartIndex,c=e.displayedData,d=e.offset,h=t.layout,v=u&&u.length,g=zj.getBaseValue(t,r,n,i),w=h==="horizontal",y=!1,m=c.map(function(S,b){var _;v?_=u[f+b]:(_=yt(S,s),Array.isArray(_)?y=!0:_=[g,_]);var O=_[1]==null||v&&yt(S,s)==null;return w?{x:Vu({axis:n,ticks:a,bandSize:l,entry:S,index:b}),y:O?null:i.scale(_[1]),value:_,payload:S}:{x:O?null:n.scale(_[1]),y:Vu({axis:i,ticks:o,bandSize:l,entry:S,index:b}),value:_,payload:S}}),x;return v||y?x=m.map(function(S){var b=Array.isArray(S.value)?S.value[0]:null;return w?{x:S.x,y:b!=null&&S.y!=null?i.scale(b):null}:{x:b!=null?n.scale(b):null,y:S.y}}):x=w?i.scale(g):n.scale(g),an({points:m,baseLine:x,layout:h,isRange:y},d)});wr(Ln,"renderDotItem",function(e,t){var r;if(E.isValidElement(e))r=E.cloneElement(e,t);else if(ne(e))r=e(t);else{var n=ue("recharts-area-dot",typeof e!="boolean"?e.className:""),i=t.key,a=Fj(t,jG);r=E.createElement(hf,ei({},a,{key:i,className:n}))}return r});function Aa(e){"@babel/helpers - typeof";return Aa=typeof Symbol=="function"&&typeof Symbol.iterator=="symbol"?function(t){return typeof t}:function(t){return t&&typeof Symbol=="function"&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Aa(e)}function MG(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function IG(e,t){for(var r=0;re.length)&&(t=e.length);for(var r=0,n=new Array(t);r=0)&&Object.prototype.propertyIsEnumerable.call(e,n)&&(r[n]=e[n])}return r}function wX(e,t){if(e==null)return{};var r={};for(var n in e)if(Object.prototype.hasOwnProperty.call(e,n)){if(t.indexOf(n)>=0)continue;r[n]=e[n]}return r}function SX(e,t){if(!(e instanceof t))throw new TypeError("Cannot call a class as a function")}function _X(e,t){for(var r=0;re.length)&&(t=e.length);for(var r=0,n=new Array(t);r0?o:t&&t.length&&K(i)&&K(a)?t.slice(i,a+1):[]};function nP(e){return e==="number"?[0,"auto"]:void 0}var Wh=function(t,r,n,i){var a=t.graphicalItems,o=t.tooltipAxis,l=Sf(r,t);return n<0||!a||!a.length||n>=l.length?null:a.reduce(function(s,u){var f,c=(f=u.props.data)!==null&&f!==void 0?f:r;c&&t.dataStartIndex+t.dataEndIndex!==0&&t.dataEndIndex-t.dataStartIndex>=n&&(c=c.slice(t.dataStartIndex,t.dataEndIndex+1));var d;if(o.dataKey&&!o.allowDuplicatedCategory){var h=c===void 0?l:c;d=hu(h,o.dataKey,i)}else d=c&&c[n]||l[n];return d?[].concat(ka(s),[qO(u,d)]):s},[])},e1=function(t,r,n,i){var a=i||{x:t.chartX,y:t.chartY},o=IX(a,n),l=t.orderedTooltipTicks,s=t.tooltipAxis,u=t.tooltipTicks,f=L5(o,l,u,s);if(f>=0&&u){var c=u[f]&&u[f].value,d=Wh(t,r,f,c),h=DX(n,l,f,a);return{activeTooltipIndex:f,activeLabel:c,activePayload:d,activeCoordinate:h}}return null},LX=function(t,r){var n=r.axes,i=r.graphicalItems,a=r.axisType,o=r.axisIdKey,l=r.stackGroups,s=r.dataStartIndex,u=r.dataEndIndex,f=t.layout,c=t.children,d=t.stackOffset,h=KO(f,a);return n.reduce(function(v,g){var w,y=g.type.defaultProps!==void 0?z(z({},g.type.defaultProps),g.props):g.props,m=y.type,x=y.dataKey,S=y.allowDataOverflow,b=y.allowDuplicatedCategory,_=y.scale,O=y.ticks,j=y.includeHidden,P=y[o];if(v[P])return v;var T=Sf(t.data,{graphicalItems:i.filter(function(I){var X,J=o in I.props?I.props[o]:(X=I.type.defaultProps)===null||X===void 0?void 0:X[o];return J===P}),dataStartIndex:s,dataEndIndex:u}),N=T.length,k,R,C;uX(y.domain,S,m)&&(k=oh(y.domain,null,S),h&&(m==="number"||_!=="auto")&&(C=Co(T,x,"category")));var D=nP(m);if(!k||k.length===0){var U,W=(U=y.domain)!==null&&U!==void 0?U:D;if(x){if(k=Co(T,x,m),m==="category"&&h){var $=jC(k);b&&$?(R=k,k=rc(0,N)):b||(k=Nb(W,k,g).reduce(function(I,X){return I.indexOf(X)>=0?I:[].concat(ka(I),[X])},[]))}else if(m==="category")b?k=k.filter(function(I){return I!==""&&!oe(I)}):k=Nb(W,k,g).reduce(function(I,X){return I.indexOf(X)>=0||X===""||oe(X)?I:[].concat(ka(I),[X])},[]);else if(m==="number"){var L=U5(T,i.filter(function(I){var X,J,te=o in I.props?I.props[o]:(X=I.type.defaultProps)===null||X===void 0?void 0:X[o],me="hide"in I.props?I.props.hide:(J=I.type.defaultProps)===null||J===void 0?void 0:J.hide;return te===P&&(j||!me)}),x,a,f);L&&(k=L)}h&&(m==="number"||_!=="auto")&&(C=Co(T,x,"category"))}else h?k=rc(0,N):l&&l[P]&&l[P].hasStack&&m==="number"?k=d==="expand"?[0,1]:XO(l[P].stackGroups,s,u):k=VO(T,i.filter(function(I){var X=o in I.props?I.props[o]:I.type.defaultProps[o],J="hide"in I.props?I.props.hide:I.type.defaultProps.hide;return X===P&&(j||!J)}),m,f,!0);if(m==="number")k=zh(c,k,P,a,O),W&&(k=oh(W,k,S));else if(m==="category"&&W){var F=W,M=k.every(function(I){return F.indexOf(I)>=0});M&&(k=F)}}return z(z({},v),{},ee({},P,z(z({},y),{},{axisType:a,domain:k,categoricalDomain:C,duplicateDomain:R,originalDomain:(w=y.domain)!==null&&w!==void 0?w:D,isCategorical:h,layout:f})))},{})},RX=function(t,r){var n=r.graphicalItems,i=r.Axis,a=r.axisType,o=r.axisIdKey,l=r.stackGroups,s=r.dataStartIndex,u=r.dataEndIndex,f=t.layout,c=t.children,d=Sf(t.data,{graphicalItems:n,dataStartIndex:s,dataEndIndex:u}),h=d.length,v=KO(f,a),g=-1;return n.reduce(function(w,y){var m=y.type.defaultProps!==void 0?z(z({},y.type.defaultProps),y.props):y.props,x=m[o],S=nP("number");if(!w[x]){g++;var b;return v?b=rc(0,h):l&&l[x]&&l[x].hasStack?(b=XO(l[x].stackGroups,s,u),b=zh(c,b,x,a)):(b=oh(S,VO(d,n.filter(function(_){var O,j,P=o in _.props?_.props[o]:(O=_.type.defaultProps)===null||O===void 0?void 0:O[o],T="hide"in _.props?_.props.hide:(j=_.type.defaultProps)===null||j===void 0?void 0:j.hide;return P===x&&!T}),"number",f),i.defaultProps.allowDataOverflow),b=zh(c,b,x,a)),z(z({},w),{},ee({},x,z(z({axisType:a},i.defaultProps),{},{hide:!0,orientation:qt(NX,"".concat(a,".").concat(g%2),null),domain:b,originalDomain:S,isCategorical:v,layout:f})))}return w},{})},BX=function(t,r){var n=r.axisType,i=n===void 0?"xAxis":n,a=r.AxisComp,o=r.graphicalItems,l=r.stackGroups,s=r.dataStartIndex,u=r.dataEndIndex,f=t.children,c="".concat(i,"Id"),d=Yt(f,a),h={};return d&&d.length?h=LX(t,{axes:d,graphicalItems:o,axisType:i,axisIdKey:c,stackGroups:l,dataStartIndex:s,dataEndIndex:u}):o&&o.length&&(h=RX(t,{Axis:a,graphicalItems:o,axisType:i,axisIdKey:c,stackGroups:l,dataStartIndex:s,dataEndIndex:u})),h},zX=function(t){var r=fn(t),n=Rr(r,!1,!0);return{tooltipTicks:n,orderedTooltipTicks:cm(n,function(i){return i.coordinate}),tooltipAxis:r,tooltipAxisBandSize:Ku(r,n)}},t1=function(t){var r=t.children,n=t.defaultShowTooltip,i=Nt(r,ga),a=0,o=0;return t.data&&t.data.length!==0&&(o=t.data.length-1),i&&i.props&&(i.props.startIndex>=0&&(a=i.props.startIndex),i.props.endIndex>=0&&(o=i.props.endIndex)),{chartX:0,chartY:0,dataStartIndex:a,dataEndIndex:o,activeTooltipIndex:-1,isTooltipActive:!!n}},FX=function(t){return!t||!t.length?!1:t.some(function(r){var n=zr(r&&r.type);return n&&n.indexOf("Bar")>=0})},r1=function(t){return t==="horizontal"?{numericAxisName:"yAxis",cateAxisName:"xAxis"}:t==="vertical"?{numericAxisName:"xAxis",cateAxisName:"yAxis"}:t==="centric"?{numericAxisName:"radiusAxis",cateAxisName:"angleAxis"}:{numericAxisName:"angleAxis",cateAxisName:"radiusAxis"}},UX=function(t,r){var n=t.props,i=t.graphicalItems,a=t.xAxisMap,o=a===void 0?{}:a,l=t.yAxisMap,s=l===void 0?{}:l,u=n.width,f=n.height,c=n.children,d=n.margin||{},h=Nt(c,ga),v=Nt(c,qi),g=Object.keys(s).reduce(function(b,_){var O=s[_],j=O.orientation;return!O.mirror&&!O.hide?z(z({},b),{},ee({},j,b[j]+O.width)):b},{left:d.left||0,right:d.right||0}),w=Object.keys(o).reduce(function(b,_){var O=o[_],j=O.orientation;return!O.mirror&&!O.hide?z(z({},b),{},ee({},j,qt(b,"".concat(j))+O.height)):b},{top:d.top||0,bottom:d.bottom||0}),y=z(z({},w),g),m=y.bottom;h&&(y.bottom+=h.props.height||ga.defaultProps.height),v&&r&&(y=z5(y,i,n,r));var x=u-y.left-y.right,S=f-y.top-y.bottom;return z(z({brushBottom:m},y),{},{width:Math.max(x,0),height:Math.max(S,0)})},WX=function(t,r){if(r==="xAxis")return t[r].width;if(r==="yAxis")return t[r].height},Xm=function(t){var r=t.chartName,n=t.GraphicalChild,i=t.defaultTooltipEventType,a=i===void 0?"axis":i,o=t.validateTooltipEventTypes,l=o===void 0?["axis"]:o,s=t.axisComponents,u=t.legendContent,f=t.formatAxisMap,c=t.defaultProps,d=function(y,m){var x=m.graphicalItems,S=m.stackGroups,b=m.offset,_=m.updateId,O=m.dataStartIndex,j=m.dataEndIndex,P=y.barSize,T=y.layout,N=y.barGap,k=y.barCategoryGap,R=y.maxBarSize,C=r1(T),D=C.numericAxisName,U=C.cateAxisName,W=FX(x),$=[];return x.forEach(function(L,F){var M=Sf(y.data,{graphicalItems:[L],dataStartIndex:O,dataEndIndex:j}),I=L.type.defaultProps!==void 0?z(z({},L.type.defaultProps),L.props):L.props,X=I.dataKey,J=I.maxBarSize,te=I["".concat(D,"Id")],me=I["".concat(U,"Id")],Re={},ze=s.reduce(function(Rn,Bn){var _f=m["".concat(Bn.axisType,"Map")],Zm=I["".concat(Bn.axisType,"Id")];_f&&_f[Zm]||Bn.axisType==="zAxis"||pi();var Jm=_f[Zm];return z(z({},Rn),{},ee(ee({},Bn.axisType,Jm),"".concat(Bn.axisType,"Ticks"),Rr(Jm)))},Re),q=ze[U],re=ze["".concat(U,"Ticks")],ie=S&&S[te]&&S[te].hasStack&&eU(L,S[te].stackGroups),H=zr(L.type).indexOf("Bar")>=0,je=Ku(q,re),se=[],B=W&&R5({barSize:P,stackGroups:S,totalSize:WX(ze,U)});if(H){var G,Z,ce=oe(J)?R:J,$t=(G=(Z=Ku(q,re,!0))!==null&&Z!==void 0?Z:ce)!==null&&G!==void 0?G:0;se=B5({barGap:N,barCategoryGap:k,bandSize:$t!==je?$t:je,sizeList:B[me],maxBarSize:ce}),$t!==je&&(se=se.map(function(Rn){return z(z({},Rn),{},{position:z(z({},Rn.position),{},{offset:Rn.position.offset-$t/2})})}))}var rn=L&&L.type&&L.type.getComposedData;rn&&$.push({props:z(z({},rn(z(z({},ze),{},{displayedData:M,props:y,dataKey:X,item:L,bandSize:je,barPosition:se,offset:b,stackedData:ie,layout:T,dataStartIndex:O,dataEndIndex:j}))),{},ee(ee(ee({key:L.key||"item-".concat(F)},D,ze[D]),U,ze[U]),"animationId",_)),childIndex:LC(L,y.children),item:L})}),$},h=function(y,m){var x=y.props,S=y.dataStartIndex,b=y.dataEndIndex,_=y.updateId;if(!Ag({props:x}))return null;var O=x.children,j=x.layout,P=x.stackOffset,T=x.data,N=x.reverseStackOrder,k=r1(j),R=k.numericAxisName,C=k.cateAxisName,D=Yt(O,n),U=Q5(T,D,"".concat(R,"Id"),"".concat(C,"Id"),P,N),W=s.reduce(function(I,X){var J="".concat(X.axisType,"Map");return z(z({},I),{},ee({},J,BX(x,z(z({},X),{},{graphicalItems:D,stackGroups:X.axisType===R&&U,dataStartIndex:S,dataEndIndex:b}))))},{}),$=UX(z(z({},W),{},{props:x,graphicalItems:D}),m?.legendBBox);Object.keys(W).forEach(function(I){W[I]=f(x,W[I],$,I.replace("Map",""),r)});var L=W["".concat(C,"Map")],F=zX(L),M=d(x,z(z({},W),{},{dataStartIndex:S,dataEndIndex:b,updateId:_,graphicalItems:D,stackGroups:U,offset:$}));return z(z({formattedGraphicalItems:M,graphicalItems:D,offset:$,stackGroups:U},F),W)},v=function(w){function y(m){var x,S,b;return SX(this,y),b=jX(this,y,[m]),ee(b,"eventEmitterSymbol",Symbol("rechartsEventEmitter")),ee(b,"accessibilityManager",new sX),ee(b,"handleLegendBBoxUpdate",function(_){if(_){var O=b.state,j=O.dataStartIndex,P=O.dataEndIndex,T=O.updateId;b.setState(z({legendBBox:_},h({props:b.props,dataStartIndex:j,dataEndIndex:P,updateId:T},z(z({},b.state),{},{legendBBox:_}))))}}),ee(b,"handleReceiveSyncEvent",function(_,O,j){if(b.props.syncId===_){if(j===b.eventEmitterSymbol&&typeof b.props.syncMethod!="function")return;b.applySyncEvent(O)}}),ee(b,"handleBrushChange",function(_){var O=_.startIndex,j=_.endIndex;if(O!==b.state.dataStartIndex||j!==b.state.dataEndIndex){var P=b.state.updateId;b.setState(function(){return z({dataStartIndex:O,dataEndIndex:j},h({props:b.props,dataStartIndex:O,dataEndIndex:j,updateId:P},b.state))}),b.triggerSyncEvent({dataStartIndex:O,dataEndIndex:j})}}),ee(b,"handleMouseEnter",function(_){var O=b.getMouseInfo(_);if(O){var j=z(z({},O),{},{isTooltipActive:!0});b.setState(j),b.triggerSyncEvent(j);var P=b.props.onMouseEnter;ne(P)&&P(j,_)}}),ee(b,"triggeredAfterMouseMove",function(_){var O=b.getMouseInfo(_),j=O?z(z({},O),{},{isTooltipActive:!0}):{isTooltipActive:!1};b.setState(j),b.triggerSyncEvent(j);var P=b.props.onMouseMove;ne(P)&&P(j,_)}),ee(b,"handleItemMouseEnter",function(_){b.setState(function(){return{isTooltipActive:!0,activeItem:_,activePayload:_.tooltipPayload,activeCoordinate:_.tooltipPosition||{x:_.cx,y:_.cy}}})}),ee(b,"handleItemMouseLeave",function(){b.setState(function(){return{isTooltipActive:!1}})}),ee(b,"handleMouseMove",function(_){_.persist(),b.throttleTriggeredAfterMouseMove(_)}),ee(b,"handleMouseLeave",function(_){b.throttleTriggeredAfterMouseMove.cancel();var O={isTooltipActive:!1};b.setState(O),b.triggerSyncEvent(O);var j=b.props.onMouseLeave;ne(j)&&j(O,_)}),ee(b,"handleOuterEvent",function(_){var O=DC(_),j=qt(b.props,"".concat(O));if(O&&ne(j)){var P,T;/.*touch.*/i.test(O)?T=b.getMouseInfo(_.changedTouches[0]):T=b.getMouseInfo(_),j((P=T)!==null&&P!==void 0?P:{},_)}}),ee(b,"handleClick",function(_){var O=b.getMouseInfo(_);if(O){var j=z(z({},O),{},{isTooltipActive:!0});b.setState(j),b.triggerSyncEvent(j);var P=b.props.onClick;ne(P)&&P(j,_)}}),ee(b,"handleMouseDown",function(_){var O=b.props.onMouseDown;if(ne(O)){var j=b.getMouseInfo(_);O(j,_)}}),ee(b,"handleMouseUp",function(_){var O=b.props.onMouseUp;if(ne(O)){var j=b.getMouseInfo(_);O(j,_)}}),ee(b,"handleTouchMove",function(_){_.changedTouches!=null&&_.changedTouches.length>0&&b.throttleTriggeredAfterMouseMove(_.changedTouches[0])}),ee(b,"handleTouchStart",function(_){_.changedTouches!=null&&_.changedTouches.length>0&&b.handleMouseDown(_.changedTouches[0])}),ee(b,"handleTouchEnd",function(_){_.changedTouches!=null&&_.changedTouches.length>0&&b.handleMouseUp(_.changedTouches[0])}),ee(b,"handleDoubleClick",function(_){var O=b.props.onDoubleClick;if(ne(O)){var j=b.getMouseInfo(_);O(j,_)}}),ee(b,"handleContextMenu",function(_){var O=b.props.onContextMenu;if(ne(O)){var j=b.getMouseInfo(_);O(j,_)}}),ee(b,"triggerSyncEvent",function(_){b.props.syncId!==void 0&&Sd.emit(_d,b.props.syncId,_,b.eventEmitterSymbol)}),ee(b,"applySyncEvent",function(_){var O=b.props,j=O.layout,P=O.syncMethod,T=b.state.updateId,N=_.dataStartIndex,k=_.dataEndIndex;if(_.dataStartIndex!==void 0||_.dataEndIndex!==void 0)b.setState(z({dataStartIndex:N,dataEndIndex:k},h({props:b.props,dataStartIndex:N,dataEndIndex:k,updateId:T},b.state)));else if(_.activeTooltipIndex!==void 0){var R=_.chartX,C=_.chartY,D=_.activeTooltipIndex,U=b.state,W=U.offset,$=U.tooltipTicks;if(!W)return;if(typeof P=="function")D=P($,_);else if(P==="value"){D=-1;for(var L=0;L<$.length;L++)if($[L].value===_.activeLabel){D=L;break}}var F=z(z({},W),{},{x:W.left,y:W.top}),M=Math.min(R,F.x+F.width),I=Math.min(C,F.y+F.height),X=$[D]&&$[D].value,J=Wh(b.state,b.props.data,D),te=$[D]?{x:j==="horizontal"?$[D].coordinate:M,y:j==="horizontal"?I:$[D].coordinate}:rP;b.setState(z(z({},_),{},{activeLabel:X,activeCoordinate:te,activePayload:J,activeTooltipIndex:D}))}else b.setState(_)}),ee(b,"renderCursor",function(_){var O,j=b.state,P=j.isTooltipActive,T=j.activeCoordinate,N=j.activePayload,k=j.offset,R=j.activeTooltipIndex,C=j.tooltipAxisBandSize,D=b.getTooltipEventType(),U=(O=_.props.active)!==null&&O!==void 0?O:P,W=b.props.layout,$=_.key||"_recharts-cursor";return E.createElement(vX,{key:$,activeCoordinate:T,activePayload:N,activeTooltipIndex:R,chartName:r,element:_,isActive:U,layout:W,offset:k,tooltipAxisBandSize:C,tooltipEventType:D})}),ee(b,"renderPolarAxis",function(_,O,j){var P=qt(_,"type.axisType"),T=qt(b.state,"".concat(P,"Map")),N=_.type.defaultProps,k=N!==void 0?z(z({},N),_.props):_.props,R=T&&T[k["".concat(P,"Id")]];return A.cloneElement(_,z(z({},R),{},{className:ue(P,R.className),key:_.key||"".concat(O,"-").concat(j),ticks:Rr(R,!0)}))}),ee(b,"renderPolarGrid",function(_){var O=_.props,j=O.radialLines,P=O.polarAngles,T=O.polarRadius,N=b.state,k=N.radiusAxisMap,R=N.angleAxisMap,C=fn(k),D=fn(R),U=D.cx,W=D.cy,$=D.innerRadius,L=D.outerRadius;return A.cloneElement(_,{polarAngles:Array.isArray(P)?P:Rr(D,!0).map(function(F){return F.coordinate}),polarRadius:Array.isArray(T)?T:Rr(C,!0).map(function(F){return F.coordinate}),cx:U,cy:W,innerRadius:$,outerRadius:L,key:_.key||"polar-grid",radialLines:j})}),ee(b,"renderLegend",function(){var _=b.state.formattedGraphicalItems,O=b.props,j=O.children,P=O.width,T=O.height,N=b.props.margin||{},k=P-(N.left||0)-(N.right||0),R=WO({children:j,formattedGraphicalItems:_,legendWidth:k,legendContent:u});if(!R)return null;var C=R.item,D=Zx(R,mX);return A.cloneElement(C,z(z({},D),{},{chartWidth:P,chartHeight:T,margin:N,onBBoxUpdate:b.handleLegendBBoxUpdate}))}),ee(b,"renderTooltip",function(){var _,O=b.props,j=O.children,P=O.accessibilityLayer,T=Nt(j,wt);if(!T)return null;var N=b.state,k=N.isTooltipActive,R=N.activeCoordinate,C=N.activePayload,D=N.activeLabel,U=N.offset,W=(_=T.props.active)!==null&&_!==void 0?_:k;return A.cloneElement(T,{viewBox:z(z({},U),{},{x:U.left,y:U.top}),active:W,label:D,payload:W?C:[],coordinate:R,accessibilityLayer:P})}),ee(b,"renderBrush",function(_){var O=b.props,j=O.margin,P=O.data,T=b.state,N=T.offset,k=T.dataStartIndex,R=T.dataEndIndex,C=T.updateId;return A.cloneElement(_,{key:_.key||"_recharts-brush",onChange:xs(b.handleBrushChange,_.props.onChange),data:P,x:K(_.props.x)?_.props.x:N.left,y:K(_.props.y)?_.props.y:N.top+N.height+N.brushBottom-(j.bottom||0),width:K(_.props.width)?_.props.width:N.width,startIndex:k,endIndex:R,updateId:"brush-".concat(C)})}),ee(b,"renderReferenceElement",function(_,O,j){if(!_)return null;var P=b,T=P.clipPathId,N=b.state,k=N.xAxisMap,R=N.yAxisMap,C=N.offset,D=_.type.defaultProps||{},U=_.props,W=U.xAxisId,$=W===void 0?D.xAxisId:W,L=U.yAxisId,F=L===void 0?D.yAxisId:L;return A.cloneElement(_,{key:_.key||"".concat(O,"-").concat(j),xAxis:k[$],yAxis:R[F],viewBox:{x:C.left,y:C.top,width:C.width,height:C.height},clipPathId:T})}),ee(b,"renderActivePoints",function(_){var O=_.item,j=_.activePoint,P=_.basePoint,T=_.childIndex,N=_.isRange,k=[],R=O.props.key,C=O.item.type.defaultProps!==void 0?z(z({},O.item.type.defaultProps),O.item.props):O.item.props,D=C.activeDot,U=C.dataKey,W=z(z({index:T,dataKey:U,cx:j.x,cy:j.y,r:4,fill:Dm(O.item),strokeWidth:2,stroke:"#fff",payload:j.payload,value:j.value},ae(D,!1)),vu(D));return k.push(y.renderActiveDot(D,W,"".concat(R,"-activePoint-").concat(T))),P?k.push(y.renderActiveDot(D,z(z({},W),{},{cx:P.x,cy:P.y}),"".concat(R,"-basePoint-").concat(T))):N&&k.push(null),k}),ee(b,"renderGraphicChild",function(_,O,j){var P=b.filterFormatItem(_,O,j);if(!P)return null;var T=b.getTooltipEventType(),N=b.state,k=N.isTooltipActive,R=N.tooltipAxis,C=N.activeTooltipIndex,D=N.activeLabel,U=b.props.children,W=Nt(U,wt),$=P.props,L=$.points,F=$.isRange,M=$.baseLine,I=P.item.type.defaultProps!==void 0?z(z({},P.item.type.defaultProps),P.item.props):P.item.props,X=I.activeDot,J=I.hide,te=I.activeBar,me=I.activeShape,Re=!!(!J&&k&&W&&(X||te||me)),ze={};T!=="axis"&&W&&W.props.trigger==="click"?ze={onClick:xs(b.handleItemMouseEnter,_.props.onClick)}:T!=="axis"&&(ze={onMouseLeave:xs(b.handleItemMouseLeave,_.props.onMouseLeave),onMouseEnter:xs(b.handleItemMouseEnter,_.props.onMouseEnter)});var q=A.cloneElement(_,z(z({},P.props),ze));function re(Bn){return typeof R.dataKey=="function"?R.dataKey(Bn.payload):null}if(Re)if(C>=0){var ie,H;if(R.dataKey&&!R.allowDuplicatedCategory){var je=typeof R.dataKey=="function"?re:"payload.".concat(R.dataKey.toString());ie=hu(L,je,D),H=F&&M&&hu(M,je,D)}else ie=L?.[C],H=F&&M&&M[C];if(me||te){var se=_.props.activeIndex!==void 0?_.props.activeIndex:C;return[A.cloneElement(_,z(z(z({},P.props),ze),{},{activeIndex:se})),null,null]}if(!oe(ie))return[q].concat(ka(b.renderActivePoints({item:P,activePoint:ie,basePoint:H,childIndex:C,isRange:F})))}else{var B,G=(B=b.getItemByXY(b.state.activeCoordinate))!==null&&B!==void 0?B:{graphicalItem:q},Z=G.graphicalItem,ce=Z.item,$t=ce===void 0?_:ce,rn=Z.childIndex,Rn=z(z(z({},P.props),ze),{},{activeIndex:rn});return[A.cloneElement($t,Rn),null,null]}return F?[q,null,null]:[q,null]}),ee(b,"renderCustomized",function(_,O,j){return A.cloneElement(_,z(z({key:"recharts-customized-".concat(j)},b.props),b.state))}),ee(b,"renderMap",{CartesianGrid:{handler:js,once:!0},ReferenceArea:{handler:b.renderReferenceElement},ReferenceLine:{handler:js},ReferenceDot:{handler:b.renderReferenceElement},XAxis:{handler:js},YAxis:{handler:js},Brush:{handler:b.renderBrush,once:!0},Bar:{handler:b.renderGraphicChild},Line:{handler:b.renderGraphicChild},Area:{handler:b.renderGraphicChild},Radar:{handler:b.renderGraphicChild},RadialBar:{handler:b.renderGraphicChild},Scatter:{handler:b.renderGraphicChild},Pie:{handler:b.renderGraphicChild},Funnel:{handler:b.renderGraphicChild},Tooltip:{handler:b.renderCursor,once:!0},PolarGrid:{handler:b.renderPolarGrid,once:!0},PolarAngleAxis:{handler:b.renderPolarAxis},PolarRadiusAxis:{handler:b.renderPolarAxis},Customized:{handler:b.renderCustomized}}),b.clipPathId="".concat((x=m.id)!==null&&x!==void 0?x:za("recharts"),"-clip"),b.throttleTriggeredAfterMouseMove=W_(b.triggeredAfterMouseMove,(S=m.throttleDelay)!==null&&S!==void 0?S:1e3/60),b.state={},b}return EX(y,w),OX(y,[{key:"componentDidMount",value:function(){var x,S;this.addListener(),this.accessibilityManager.setDetails({container:this.container,offset:{left:(x=this.props.margin.left)!==null&&x!==void 0?x:0,top:(S=this.props.margin.top)!==null&&S!==void 0?S:0},coordinateList:this.state.tooltipTicks,mouseHandlerCallback:this.triggeredAfterMouseMove,layout:this.props.layout}),this.displayDefaultTooltip()}},{key:"displayDefaultTooltip",value:function(){var x=this.props,S=x.children,b=x.data,_=x.height,O=x.layout,j=Nt(S,wt);if(j){var P=j.props.defaultIndex;if(!(typeof P!="number"||P<0||P>this.state.tooltipTicks.length-1)){var T=this.state.tooltipTicks[P]&&this.state.tooltipTicks[P].value,N=Wh(this.state,b,P,T),k=this.state.tooltipTicks[P].coordinate,R=(this.state.offset.top+_)/2,C=O==="horizontal",D=C?{x:k,y:R}:{y:k,x:R},U=this.state.formattedGraphicalItems.find(function($){var L=$.item;return L.type.name==="Scatter"});U&&(D=z(z({},D),U.props.points[P].tooltipPosition),N=U.props.points[P].tooltipPayload);var W={activeTooltipIndex:P,isTooltipActive:!0,activeLabel:T,activePayload:N,activeCoordinate:D};this.setState(W),this.renderCursor(j),this.accessibilityManager.setIndex(P)}}}},{key:"getSnapshotBeforeUpdate",value:function(x,S){if(!this.props.accessibilityLayer)return null;if(this.state.tooltipTicks!==S.tooltipTicks&&this.accessibilityManager.setDetails({coordinateList:this.state.tooltipTicks}),this.props.layout!==x.layout&&this.accessibilityManager.setDetails({layout:this.props.layout}),this.props.margin!==x.margin){var b,_;this.accessibilityManager.setDetails({offset:{left:(b=this.props.margin.left)!==null&&b!==void 0?b:0,top:(_=this.props.margin.top)!==null&&_!==void 0?_:0}})}return null}},{key:"componentDidUpdate",value:function(x){wp([Nt(x.children,wt)],[Nt(this.props.children,wt)])||this.displayDefaultTooltip()}},{key:"componentWillUnmount",value:function(){this.removeListener(),this.throttleTriggeredAfterMouseMove.cancel()}},{key:"getTooltipEventType",value:function(){var x=Nt(this.props.children,wt);if(x&&typeof x.props.shared=="boolean"){var S=x.props.shared?"axis":"item";return l.indexOf(S)>=0?S:a}return a}},{key:"getMouseInfo",value:function(x){if(!this.container)return null;var S=this.container,b=S.getBoundingClientRect(),_=uF(b),O={chartX:Math.round(x.pageX-_.left),chartY:Math.round(x.pageY-_.top)},j=b.width/S.offsetWidth||1,P=this.inRange(O.chartX,O.chartY,j);if(!P)return null;var T=this.state,N=T.xAxisMap,k=T.yAxisMap,R=this.getTooltipEventType(),C=e1(this.state,this.props.data,this.props.layout,P);if(R!=="axis"&&N&&k){var D=fn(N).scale,U=fn(k).scale,W=D&&D.invert?D.invert(O.chartX):null,$=U&&U.invert?U.invert(O.chartY):null;return z(z({},O),{},{xValue:W,yValue:$},C)}return C?z(z({},O),C):null}},{key:"inRange",value:function(x,S){var b=arguments.length>2&&arguments[2]!==void 0?arguments[2]:1,_=this.props.layout,O=x/b,j=S/b;if(_==="horizontal"||_==="vertical"){var P=this.state.offset,T=O>=P.left&&O<=P.left+P.width&&j>=P.top&&j<=P.top+P.height;return T?{x:O,y:j}:null}var N=this.state,k=N.angleAxisMap,R=N.radiusAxisMap;if(k&&R){var C=fn(k);return Db({x:O,y:j},C)}return null}},{key:"parseEventsOfWrapper",value:function(){var x=this.props.children,S=this.getTooltipEventType(),b=Nt(x,wt),_={};b&&S==="axis"&&(b.props.trigger==="click"?_={onClick:this.handleClick}:_={onMouseEnter:this.handleMouseEnter,onDoubleClick:this.handleDoubleClick,onMouseMove:this.handleMouseMove,onMouseLeave:this.handleMouseLeave,onTouchMove:this.handleTouchMove,onTouchStart:this.handleTouchStart,onTouchEnd:this.handleTouchEnd,onContextMenu:this.handleContextMenu});var O=vu(this.props,this.handleOuterEvent);return z(z({},O),_)}},{key:"addListener",value:function(){Sd.on(_d,this.handleReceiveSyncEvent)}},{key:"removeListener",value:function(){Sd.removeListener(_d,this.handleReceiveSyncEvent)}},{key:"filterFormatItem",value:function(x,S,b){for(var _=this.state.formattedGraphicalItems,O=0,j=_.length;O({bucket:i.bucket,count:i.count,tokens:(i.token_input_other||0)+(i.token_input_cached||0)+(i.token_output||0)})),n={fill:t.dim,fontSize:10};return p.jsx(Ul,{width:"100%",height:"100%",children:p.jsxs(HX,{data:r,margin:{top:8,right:8,bottom:0,left:0},children:[p.jsx(Xa,{stroke:t.border,strokeDasharray:"3 3",vertical:!1}),p.jsx(kr,{dataKey:"bucket",tickFormatter:i=>gp(String(i)),tick:n,stroke:t.border}),p.jsx(vr,{yAxisId:"y",tickFormatter:i=>Jo(Number(i)),tick:n,stroke:t.border}),p.jsx(vr,{yAxisId:"y1",orientation:"right",tickFormatter:i=>Jo(Number(i)),tick:n,stroke:t.border}),p.jsx(wt,{}),p.jsx(ja,{yAxisId:"y",type:"monotone",dataKey:"count",name:"调用",stroke:t.accent,dot:!1}),p.jsx(ja,{yAxisId:"y1",type:"monotone",dataKey:"tokens",name:"Token",stroke:t.warn,dot:!1})]})})}function GX({data:e,colors:t}){const r=Or(du()),n={fill:t.dim,fontSize:10};return p.jsx(Ul,{width:"100%",height:"100%",children:p.jsxs(VX,{data:e,margin:{top:8,right:8,bottom:0,left:0},children:[p.jsx("defs",{children:p.jsxs("linearGradient",{id:"costGradient",x1:"0",y1:"0",x2:"0",y2:"1",children:[p.jsx("stop",{offset:"0%",stopColor:t.accent,stopOpacity:.35}),p.jsx("stop",{offset:"100%",stopColor:t.accent,stopOpacity:.02})]})}),p.jsx(Xa,{stroke:t.border,strokeDasharray:"3 3",vertical:!1}),p.jsx(kr,{dataKey:"bucket",tickFormatter:i=>gp(String(i)),tick:n,stroke:t.border}),p.jsx(vr,{tickFormatter:i=>r+Jo(Number(i)),tick:n,stroke:t.border}),p.jsx(wt,{formatter:i=>[XX(i,r),"成本"],labelFormatter:i=>gp(String(i))}),p.jsx(Ln,{type:"monotone",dataKey:"cost",name:"成本",stroke:t.accent,strokeWidth:2,fill:"url(#costGradient)",dot:!1})]})})}function XX(e,t){const r=Number(e??0);return Number.isFinite(r)?t+r.toFixed(4):t+"0"}function n1({data:e,colors:t}){const r={fill:t.dim,fontSize:10};return p.jsx(Ul,{width:"100%",height:"100%",children:p.jsxs(qm,{data:e,layout:"vertical",margin:{top:4,right:16,bottom:4,left:8},children:[p.jsx(Xa,{stroke:t.border,strokeDasharray:"3 3",horizontal:!1}),p.jsx(kr,{type:"number",tickFormatter:n=>"$"+Jo(Number(n)),tick:r,stroke:t.border}),p.jsx(vr,{type:"category",dataKey:"model",width:120,tick:r,stroke:t.border}),p.jsx(wt,{}),p.jsx(pr,{dataKey:"cost",name:"成本",fill:t.accent,radius:[0,3,3,0]})]})})}function qX({data:e,colors:t}){const r={fill:t.dim,fontSize:11},n=e.map(i=>{const a=i.other+i.cached+i.output;return a<=0?{label:i.label,other:0,cached:0,output:0}:{label:i.label,other:+(i.other/a*100).toFixed(1),cached:+(i.cached/a*100).toFixed(1),output:+(i.output/a*100).toFixed(1)}});return p.jsx(Ul,{width:"100%",height:"100%",children:p.jsxs(qm,{data:n,margin:{top:4,right:8,bottom:0,left:0},children:[p.jsx(Xa,{stroke:t.border,strokeDasharray:"3 3",vertical:!1}),p.jsx(kr,{dataKey:"label",tick:r,stroke:t.border}),p.jsx(vr,{tickFormatter:i=>`${i}%`,domain:[0,100],tick:{fill:t.dim,fontSize:10},stroke:t.border}),p.jsx(wt,{formatter:(i,a)=>[`${i}%`,a]}),p.jsx(pr,{dataKey:"other",name:"输入(非缓存)",stackId:"a",fill:t.other}),p.jsx(pr,{dataKey:"cached",name:"缓存命中",stackId:"a",fill:t.cached}),p.jsx(pr,{dataKey:"output",name:"输出",stackId:"a",fill:t.accent})]})})}function YX({data:e,colors:t}){const r={fill:t.dim,fontSize:10};return p.jsx(Ul,{width:"100%",height:"100%",children:p.jsxs(qm,{data:e,layout:"vertical",margin:{top:4,right:16,bottom:4,left:8},children:[p.jsx(Xa,{stroke:t.border,strokeDasharray:"3 3",horizontal:!1}),p.jsx(kr,{type:"number",tickFormatter:n=>Jo(Number(n)),tick:r,stroke:t.border}),p.jsx(vr,{type:"category",dataKey:"umo",width:120,tick:r,stroke:t.border}),p.jsx(wt,{}),p.jsx(pr,{dataKey:"tokens",name:"Token",fill:t.warn,radius:[0,3,3,0]})]})})}function QX({window:e,refreshNonce:t,colors:r,onNavigate:n}){const i=cg(e),a=ut(()=>fe.getOverview(e),[e,t]),o=ut(()=>fe.getTimeline(cg(e),"day"),[e,t]),l=ut(()=>fe.getCompare(e),[e,t]),s=ut(()=>fe.getAlerts(e),[e,t]),u=ut(()=>fe.getOverview("daily"),[t]),f=ut(()=>fe.getOverview("weekly"),[t]),c=ut(()=>fe.getOverview("monthly"),[t]);if(BE(()=>{a.refetch(),o.refetch(),l.refetch(),s.refetch()},3e4,!0),a.loading&&!a.data)return p.jsx(Xr,{});if(a.error)return p.jsx(Tn,{message:`加载总览失败:${a.error}`});const d=a.data,h=d?.usage||{},v=zE(e),g=[{label:"成本",value:hn(d?.cost),sub:`${du()} · ${v}`,delta:p.jsx(fg,{cmp:l.data??null,field:"cost"})},{label:"调用次数",value:pe(h.count),sub:v,delta:p.jsx(fg,{cmp:l.data??null,field:"count"})},{label:"平均缓存命中率",value:`${d?.cache_hit_rate||0}%`,sub:`${d?.cache_samples||0} 样本`},{label:"平均上下文注入",value:pe(d?.avg_injection),sub:`${d?.injection_samples||0} 样本 · token`}],w=(d?.cost_by_model||[]).slice(0,8).map(j=>({model:PS(j.model),cost:j.cost})),y=[{label:"1天",other:u.data?.usage?.token_input_other||0,cached:u.data?.usage?.token_input_cached||0,output:u.data?.usage?.token_output||0},{label:"7天",other:f.data?.usage?.token_input_other||0,cached:f.data?.usage?.token_input_cached||0,output:f.data?.usage?.token_output||0},{label:"30天",other:c.data?.usage?.token_input_other||0,cached:c.data?.usage?.token_input_cached||0,output:c.data?.usage?.token_output||0}],m=y.some(j=>j.other+j.cached+j.output>0),x=(d?.top_sessions||[]).slice(0,8).reverse().map(j=>({umo:pu(j.umo),tokens:j.tokens,cost:j.cost||0})),S=(d?.top_sessions_by_cost||[]).slice(0,8).map(j=>({model:pu(j.umo),cost:j.cost||0})),b=o.data?.series??[],_=o.data?.cost_series??[],O=s.data||[];return p.jsxs("div",{children:[O.length>0&&n&&p.jsx(UE,{alerts:O,onNavigate:n}),p.jsx(zv,{items:g}),p.jsx(KE,{}),p.jsxs("div",{className:"grid-2",children:[p.jsx(Ne,{title:`用量趋势(近 ${i} 天)`,children:b.length?p.jsx("div",{className:"chart-box",children:p.jsx(KX,{series:b,colors:r})}):p.jsx(Vt,{text:"暂无时序数据"})}),p.jsx(Ne,{title:`成本趋势(近 ${i} 天)`,children:_.length?p.jsx("div",{className:"chart-box",children:p.jsx(GX,{data:_,colors:r})}):p.jsx(Vt,{text:"暂无成本时序数据"})})]}),p.jsxs("div",{className:"grid-2",children:[p.jsx(Ne,{title:"按模型成本",children:w.length?p.jsx("div",{className:"chart-box",children:p.jsx(n1,{data:w,colors:r})}):p.jsx(Vt,{text:"暂无模型成本数据"})}),p.jsx(Ne,{title:"Top 会话(按成本)",children:S.length?p.jsx("div",{className:"chart-box",children:p.jsx(n1,{data:S,colors:r})}):p.jsx(Vt,{text:"暂无会话数据"})})]}),p.jsxs("div",{className:"grid-2",children:[p.jsx(Ne,{title:"Top 会话(按 token)",children:x.length?p.jsx("div",{className:"chart-box",children:p.jsx(YX,{data:x,colors:r})}):p.jsx(Vt,{text:"暂无会话数据"})}),p.jsx(Ne,{title:"Token 构成",children:m?p.jsx("div",{className:"chart-box",children:p.jsx(qX,{data:y,colors:r})}):p.jsx(Vt,{text:"暂无 token 数据"})})]})]})}function yc({ratio:e,warnAt:t=80,badAt:r=100,children:n}){const i=Math.min(100,Math.max(0,e||0)),a=i>=r?"bad":i>=t?"warn":"";return p.jsxs("div",{className:"row",style:{alignItems:"center",gap:8},children:[p.jsx("div",{className:"bar-wrap",style:{flex:1},children:p.jsx("div",{className:`bar ${a}`,style:{width:`${i}%`}})}),n!=null&&p.jsx("span",{children:n})]})}const ZX={preset:"7d",start:"",end:"",model:"",umo:"",provider:"",order_by:"created_at",order_dir:"desc"},ho=50;function JX(e){const t=new Date,r=t.toISOString().slice(0,10);if(e.preset==="today")return{start:r,end:r};if(e.preset==="7d"){const n=new Date(t);return n.setDate(n.getDate()-6),{start:n.toISOString().slice(0,10),end:r}}if(e.preset==="30d"){const n=new Date(t);return n.setDate(n.getDate()-29),{start:n.toISOString().slice(0,10),end:r}}return{start:e.start||"",end:e.end||""}}function eq({refreshNonce:e}){const[t,r]=A.useState(ZX),[n,i]=A.useState("model"),[a,o]=A.useState(1),l=A.useMemo(()=>JX(t),[t.preset,t.start,t.end]),u=(ut(()=>fe.getOverview("daily"),[e]).data?.cost_by_model||[]).map(S=>S.model),f=ut(()=>fe.getRecordsAggregate({by:n,umo:t.umo,provider:t.provider,model:t.model,start:l.start,end:l.end}),[n,t.umo,t.provider,t.model,l.start,l.end,e]),c=ut(()=>fe.getRecords({umo:t.umo,provider:t.provider,model:t.model,start:l.start,end:l.end,order_by:t.order_by,order_dir:t.order_dir,limit:1e3}),[t.umo,t.provider,t.model,l.start,l.end,t.order_by,t.order_dir,e]),d=S=>{r(b=>({...b,...S})),o(1)},h=f.data?.groups||[],v=c.data||[],g=h.reduce((S,b)=>S+(b.cost||0),0),w=v.reduce((S,b)=>(S.input+=b.token_input_other||0,S.cached+=b.token_input_cached||0,S.output+=b.token_output||0,S.creation+=b.cache_creation||0,S.cost+=b.cost||0,S),{input:0,cached:0,output:0,creation:0,cost:0}),y=Math.max(1,Math.ceil(v.length/ho)),m=Math.min(a,y),x=v.slice((m-1)*ho,m*ho);return p.jsxs("div",{children:[p.jsxs("div",{className:"toolbar records-toolbar",children:[p.jsx(fu,{value:t.preset,onChange:S=>d({preset:S}),options:[{value:"today",label:"今日"},{value:"7d",label:"7日"},{value:"30d",label:"30日"},{value:"custom",label:"自定义"}]}),t.preset==="custom"&&p.jsxs("span",{className:"custom-range",children:[p.jsx("input",{type:"date",value:t.start,onChange:S=>d({start:S.target.value,preset:"custom"})})," ~ ",p.jsx("input",{type:"date",value:t.end,onChange:S=>d({end:S.target.value,preset:"custom"})})]}),p.jsxs("select",{value:t.model,onChange:S=>d({model:S.target.value}),children:[p.jsx("option",{value:"",children:"全部模型"}),u.map(S=>p.jsx("option",{value:S,children:S},S))]}),p.jsx("input",{defaultValue:t.umo,placeholder:"按会话 UMO 筛选",onBlur:S=>d({umo:S.target.value.trim()}),onKeyDown:S=>{S.key==="Enter"&&d({umo:S.target.value.trim()})}}),p.jsx("input",{defaultValue:t.provider,placeholder:"Provider ID",onBlur:S=>d({provider:S.target.value.trim()}),onKeyDown:S=>{S.key==="Enter"&&d({provider:S.target.value.trim()})}}),p.jsxs("select",{value:t.order_by,onChange:S=>d({order_by:S.target.value}),children:[p.jsx("option",{value:"created_at",children:"按时间"}),p.jsx("option",{value:"token_input_other",children:"按输入"}),p.jsx("option",{value:"token_output",children:"按输出"})]}),p.jsx("button",{className:"btn",title:"升降序",onClick:()=>d({order_dir:t.order_dir==="desc"?"asc":"desc"}),children:t.order_dir==="desc"?"↓":"↑"})]}),p.jsxs(Ne,{className:"agg-panel",children:[p.jsxs("div",{className:"agg-head",children:[p.jsx("h2",{style:{margin:0},children:"交叉聚合"}),p.jsx(fu,{variant:"weak",value:n,onChange:S=>i(S),options:[{value:"model",label:"按模型"},{value:"umo",label:"按会话"}]})]}),f.loading&&!f.data?p.jsx(Xr,{message:"加载聚合…"}):f.error?p.jsxs("div",{className:"muted",children:["聚合失败:",f.error]}):h.length===0?p.jsx(Vt,{text:"暂无聚合数据"}):p.jsxs("table",{children:[p.jsx("thead",{children:p.jsxs("tr",{children:[p.jsx("th",{children:n==="model"?"模型":"会话"}),p.jsx("th",{children:"调用"}),p.jsx("th",{children:"token 合计"}),p.jsx("th",{children:"成本"}),p.jsx("th",{children:"token 占比"}),p.jsx("th",{children:"费用占比"})]})}),p.jsx("tbody",{children:h.map(S=>{const b=g?Math.round(S.cost*1e3/g)/10:0;return p.jsxs("tr",{children:[p.jsx("td",{className:"mono",children:n==="model"?PS(S.key):pu(S.key)}),p.jsx("td",{children:pe(S.count)}),p.jsx("td",{children:pe(S.tokens)}),p.jsx("td",{children:hn(S.cost)}),p.jsx("td",{style:{minWidth:120},children:p.jsxs(yc,{ratio:S.pct,warnAt:25,badAt:50,children:[S.pct,"%"]})}),p.jsx("td",{style:{minWidth:120},children:p.jsxs(yc,{ratio:b,warnAt:25,badAt:50,children:[b,"%"]})})]},S.key)})})]})]}),p.jsxs(Ne,{children:[c.loading&&!c.data?p.jsx(Xr,{}):c.error?p.jsx(Tn,{message:`加载失败:${c.error}`}):v.length===0?p.jsx(Vt,{text:"暂无明细记录"}):p.jsxs("table",{children:[p.jsx("thead",{children:p.jsxs("tr",{children:[p.jsx("th",{children:"时间"}),p.jsx("th",{children:"会话"}),p.jsx("th",{children:"模型"}),p.jsx("th",{children:"Provider"}),p.jsx("th",{children:"输入"}),p.jsx("th",{children:"缓存"}),p.jsx("th",{children:"输出"}),p.jsx("th",{children:"cache写入"}),p.jsx("th",{children:"注入"}),p.jsx("th",{children:"成本"})]})}),p.jsx("tbody",{children:x.map((S,b)=>p.jsxs("tr",{children:[p.jsx("td",{children:Bv(S.created_at)}),p.jsx("td",{className:"mono",title:S.umo||"",children:pu(S.umo)}),p.jsx("td",{className:"mono",title:S.provider_model||"",children:S.provider_model||"-"}),p.jsx("td",{className:"mono",children:S.provider_id||"-"}),p.jsx("td",{children:pe(S.token_input_other)}),p.jsx("td",{children:pe(S.token_input_cached)}),p.jsx("td",{children:pe(S.token_output)}),p.jsx("td",{children:pe(S.cache_creation)}),p.jsx("td",{children:S.injection_total==null?"-":pe(S.injection_total)}),p.jsx("td",{children:S.cost_original!=null&&S.currency_symbol?hn(S.cost_original,S.currency_symbol):hn(S.cost)})]},b))}),p.jsx("tfoot",{children:p.jsxs("tr",{className:"sum-row",children:[p.jsxs("td",{colSpan:4,children:["合计(",v.length," 条)"]}),p.jsx("td",{children:pe(w.input)}),p.jsx("td",{children:pe(w.cached)}),p.jsx("td",{children:pe(w.output)}),p.jsx("td",{children:pe(w.creation)}),p.jsx("td",{}),p.jsx("td",{children:hn(w.cost)})]})})]}),v.length>ho&&p.jsxs("div",{className:"pager",children:[p.jsxs("div",{className:"pager-nav",children:[p.jsx("button",{type:"button",className:"btn",disabled:m<=1,onClick:()=>o(S=>Math.max(1,S-1)),children:"‹ 上一页"}),p.jsx("select",{className:"pager-jump",value:m,onChange:S=>o(+S.target.value),children:Array.from({length:y},(S,b)=>p.jsxs("option",{value:b+1,children:["第 ",b+1," / ",y," 页"]},b))}),p.jsx("button",{type:"button",className:"btn",disabled:m>=y,onClick:()=>o(S=>Math.min(y,S+1)),children:"下一页 ›"})]}),p.jsxs("span",{className:"muted small",children:["共 ",v.length," 条 · 每页 ",ho," 条",v.length>=1e3?"(仅最近 1000 条)":""]})]})]})]})}function Ym(e,t,r={}){const{delay:n=800,toastMs:i=1500,enabled:a=!0}=r,o=A.useMemo(()=>JSON.stringify(e),[e]),l=A.useRef(o);l.current=o;const s=A.useRef(e);s.current=e;const u=A.useRef(t);u.current=t;const[f,c]=A.useState("idle"),[d,h]=A.useState(void 0),v=A.useRef(null),g=A.useRef(!1),w=A.useRef(null),y=A.useRef(null),m=A.useRef(!1),x=A.useCallback(()=>{y.current!==null&&(clearTimeout(y.current),y.current=null)},[]),S=A.useCallback(async()=>{if(!(v.current!==null&&l.current===v.current)){if(m.current){w.current=setTimeout(()=>{S()},n);return}m.current=!0,c("saving");try{await u.current(s.current),v.current=l.current,c("saved"),h(void 0),x(),y.current=setTimeout(()=>c("idle"),i)}catch(_){c("error"),h(_ instanceof Error?_.message:String(_)),x(),y.current=setTimeout(()=>c("idle"),i*3)}finally{m.current=!1}}},[n,i,x]);A.useLayoutEffect(()=>{a&&!g.current&&(v.current=l.current),g.current=a},[a]),A.useEffect(()=>{if(a&&v.current!==null&&o!==v.current)return w.current!==null&&clearTimeout(w.current),w.current=setTimeout(()=>{S()},n),()=>{w.current!==null&&(clearTimeout(w.current),w.current=null)}},[o,a,n,S]),A.useEffect(()=>()=>{w.current!==null&&clearTimeout(w.current),x(),a&&v.current!==null&&!m.current&&l.current!==v.current&&u.current(s.current).catch(()=>{})},[]);const b=A.useCallback(async()=>{w.current!==null&&(clearTimeout(w.current),w.current=null),a&&v.current!==null&&!m.current&&l.current!==v.current&&await S()},[a,S]);return{status:f,error:d,flush:b}}const tq=[{key:"global_daily",label:"全局每日"},{key:"global_monthly",label:"全局每月"},{key:"per_session_daily",label:"单会话每日",note:"代表值"},{key:"per_user_daily",label:"单用户每日",note:"代表值"},{key:"per_model_daily",label:"单模型每日",note:"代表值"}];function rq({limits:e,limitsCost:t,budgetsCostCurrency:r,dimensions:n,onChangeLimit:i,onChangeLimitCost:a,onChangeCostCurrency:o}){return p.jsxs("table",{className:"budget-table",children:[p.jsx("thead",{children:p.jsxs("tr",{children:[p.jsx("th",{style:{minWidth:120},children:"维度"}),p.jsx("th",{style:{width:"42%"},children:"Token(限额 / 消耗)"}),p.jsx("th",{style:{width:"42%"},children:"花费 $(限额 / 消耗)"})]})}),p.jsx("tbody",{children:tq.map(l=>{const s=n[l.key]||{},u=s.token||{limit:0,used:0,ratio:0,exceeded:!1},f=s.cost||{limit:0,used:0,ratio:0,exceeded:!1},c=!!(u.exceeded||f.exceeded);return p.jsxs("tr",{className:c?"exceeded":"",children:[p.jsxs("td",{children:[p.jsx("div",{className:"budget-dim-label",children:l.label}),l.note&&p.jsx("div",{className:"muted small",children:l.note})]}),p.jsx("td",{children:p.jsxs("div",{className:"budget-cell",children:[p.jsx("input",{type:"number",min:"0",className:"budget-input",value:e[l.key]||0,onChange:d=>i(l.key,d.target.value),style:{width:110}}),p.jsxs("div",{className:"muted small budget-cell-used",children:[pe(u.used)," / ",pe(e[l.key]||0)]}),u.limit>0?p.jsxs(yc,{ratio:u.ratio,children:[u.ratio||0,"%"]}):p.jsx("div",{className:"muted small",children:"未设上限"}),u.top_key&&p.jsx("div",{className:"muted small",children:u.top_key})]})}),p.jsx("td",{children:p.jsxs("div",{className:"budget-cell",children:[p.jsx("span",{className:"muted small",children:Or(r[l.key]||"")}),p.jsx("input",{type:"number",min:"0",step:"0.01",className:"budget-input",value:t[l.key]||0,onChange:d=>a(l.key,d.target.value),style:{width:110}}),p.jsxs("select",{className:"budget-input",value:r[l.key]||"",onChange:d=>o(l.key,d.target.value),title:"花费货币(留空=主货币)",style:{width:96},children:[p.jsx("option",{value:"",children:"主货币"}),Ic.map(d=>p.jsxs("option",{value:d,children:[d," (",Or(d),")"]},d))]}),p.jsxs("div",{className:"muted small budget-cell-used",children:[hn(f.used,r[l.key]||"")," /"," ",hn(t[l.key]||0,r[l.key]||"")]}),f.limit>0?p.jsxs(yc,{ratio:f.ratio,children:[f.ratio||0,"%"]}):p.jsx("div",{className:"muted small",children:"未设上限"}),f.top_key&&p.jsx("div",{className:"muted small",children:f.top_key})]})})]},l.key)})})]})}const i1={umo:"会话",provider:"Provider",user:"用户"},nq={umo:"如 qq:12345 / platform:session_id",provider:"",user:"发送者 ID(QQ / 微信 / 钉钉)"},a1={stop:"硬拦截",fallback:"切备用",warn:"仅警告"};function iq({row:e,index:t,total:r,providers:n,fallbackProviders:i,onChange:a,onMove:o,onDelete:l}){const s=e.target_type,u=e.on_exceeded,f=(e.token_limit||0)>0,c=(e.cost_limit||0)>0,d=f||c;return p.jsxs("div",{className:`override-card ${e.enabled?"":"is-disabled"}`,children:[p.jsxs("div",{className:"override-main",children:[p.jsx("input",{type:"checkbox",className:"ov-enable",checked:e.enabled,onChange:h=>a({enabled:h.target.checked}),title:"启用"}),p.jsx("span",{className:"override-idx",children:t+1}),p.jsx("span",{className:"ov-sep",children:"当"}),p.jsx("select",{className:"override-target",value:s,onChange:h=>a({target_type:h.target.value,target_value:""}),children:Object.keys(i1).map(h=>p.jsx("option",{value:h,children:i1[h]},h))}),s==="provider"?p.jsxs("select",{className:"override-value",value:e.target_value,onChange:h=>a({target_value:h.target.value}),children:[p.jsx("option",{value:"",children:"选择 Provider"}),(n||[]).map(h=>p.jsxs("option",{value:h.id,children:[h.id,h.model?` (${h.model})`:""]},h.id))]}):p.jsx("input",{className:"override-value",value:e.target_value,onChange:h=>a({target_value:h.target.value}),placeholder:nq[s]}),p.jsx("span",{className:"ov-sep ov-sep-dot",children:"·"}),p.jsxs("label",{className:"ov-limit",children:[p.jsx("span",{className:"muted small",children:"Token≤"}),p.jsx("input",{type:"number",min:"0",className:"budget-input",value:e.token_limit||0,onChange:h=>a({token_limit:Math.max(0,+h.target.value||0)}),placeholder:"0"})]}),p.jsxs("label",{className:"ov-limit",children:[p.jsxs("span",{className:"muted small",children:[Or(e.cost_currency||du()),"≤"]}),p.jsx("input",{type:"number",min:"0",step:"0.01",className:"budget-input",value:e.cost_limit||0,onChange:h=>a({cost_limit:Math.max(0,+h.target.value||0)}),placeholder:"0"}),p.jsxs("select",{className:"budget-input",value:e.cost_currency||"",onChange:h=>a({cost_currency:h.target.value}),title:"花费货币(留空=主货币)",children:[p.jsx("option",{value:"",children:"主货币"}),Ic.map(h=>p.jsxs("option",{value:h,children:[h," (",Or(h),")"]},h))]})]}),p.jsx("span",{className:"ov-sep",children:"→"}),p.jsx("select",{className:"override-on",value:u,onChange:h=>a({on_exceeded:h.target.value}),title:"超限处理",children:Object.keys(a1).map(h=>p.jsx("option",{value:h,children:a1[h]},h))}),p.jsxs("div",{className:"override-ops",children:[p.jsx("button",{type:"button",className:"move-btn",disabled:t===0,onClick:()=>o("up"),title:"上移",children:"↑"}),p.jsx("button",{type:"button",className:"move-btn",disabled:t===r-1,onClick:()=>o("down"),title:"下移",children:"↓"}),p.jsx("button",{type:"button",className:"move-btn del",onClick:l,title:"删除",children:"✕"})]})]}),d&&p.jsxs("div",{className:"override-status",children:[f&&p.jsx(o1,{label:"token",used:e.current?.token?.used||0,limit:e.token_limit||0,ratio:e.current?.token?.ratio||0,exceeded:!!e.current?.token?.exceeded,fmt:pe}),c&&p.jsx(o1,{label:"cost",used:e.current?.cost?.used||0,limit:e.cost_limit||0,ratio:e.current?.cost?.ratio||0,exceeded:!!e.current?.cost?.exceeded,fmt:hn,prefix:Or(e.cost_currency||du())})]}),u==="stop"&&p.jsxs("div",{className:"override-extra",children:[p.jsx("span",{className:"muted small",children:"拦截文案"}),p.jsx("input",{className:"budget-input",value:e.stop_message||"",placeholder:"留空 = 默认文案(含维度 / used / limit)",onChange:h=>a({stop_message:h.target.value})})]}),u==="fallback"&&p.jsxs("div",{className:"override-extra",children:[p.jsx("span",{className:"muted small",children:"备用(按序)"}),p.jsx(aq,{selected:e.fallback_provider_ids,candidates:i,onChange:h=>a({fallback_provider_ids:h})}),p.jsxs("label",{className:"ov-limit",style:{marginLeft:"auto"},children:[p.jsx("span",{className:"muted small",children:"history 截断"}),p.jsx("input",{type:"number",min:"0",className:"budget-input",value:e.fallback_token_limit||0,onChange:h=>a({fallback_token_limit:Math.max(0,+h.target.value||0)}),style:{width:80}}),p.jsx("span",{className:"muted small",children:"0=不限"})]})]})]})}function o1({label:e,used:t,limit:r,ratio:n,exceeded:i,fmt:a,prefix:o=""}){const l=Math.min(100,Math.max(0,n||0)),s=i?"bad":l>=80?"warn":"";return p.jsxs("span",{className:`ov-stat ${s}`,children:[p.jsx("span",{className:"muted small",children:e}),p.jsx("i",{className:"ov-bar",style:{backgroundSize:`${l}% 100%`}}),p.jsxs("span",{className:"ov-pct",children:[l,"%"]}),p.jsxs("span",{className:"muted small",children:[o,a(t)," / ",o,a(r)]})]})}function aq({selected:e,candidates:t,onChange:r}){const n=t.length===0;return p.jsxs("div",{className:"fb-picker",children:[e.map((i,a)=>p.jsxs("span",{className:"provider-tag",children:[i,p.jsx("button",{type:"button",className:"tag-del",onClick:()=>{const o=e.slice();o.splice(a,1),r(o)},children:"✕"})]},`${i}-${a}`)),n?p.jsx("span",{className:"muted small",children:"请先在下方「备用 Provider 库」添加(否则将降级为硬拦截)"}):p.jsxs("select",{className:"fb-add",value:"",onChange:i=>{const a=i.target.value;a&&!e.includes(a)&&r([...e,a]),i.target.value=""},children:[p.jsx("option",{value:"",children:"+ 从备用库添加"}),t.map(i=>p.jsxs("option",{value:i.id,children:[i.id,i.note?` · ${i.note}`:""]},i.id))]})]})}function oq({overrides:e,providers:t,fallbackProviders:r,onChange:n,onMove:i,onDelete:a,onAdd:o}){return p.jsxs("div",{className:"overrides-panel",children:[p.jsxs("div",{className:"muted small",style:{marginBottom:8},children:["按序匹配,第一条命中即生效:token / cost 任一超限 → 按本规则的",p.jsx("b",{children:" 超限处理 "}),"执行(不走全局);未命中或未超限 → 回落全局。"]}),e.length===0?p.jsx("div",{className:"muted small",style:{textAlign:"center",padding:"20px 0"},children:"暂无规则(仅按全局预算生效)"}):p.jsx("div",{className:"override-list",children:e.map((l,s)=>p.jsx(iq,{row:l,index:s,total:e.length,providers:t,fallbackProviders:r,onChange:u=>n(s,u),onMove:u=>i(s,u),onDelete:()=>a(s)},l.id||`ov-${s}`))}),p.jsx("div",{style:{marginTop:8},children:p.jsx("button",{type:"button",className:"btn",onClick:o,children:"+ 添加规则"})})]})}function lq({providers:e,realProviders:t,onChange:r,onDelete:n,onAdd:i}){const a="fb-provider-options";return p.jsxs("div",{className:"fallback-providers",children:[p.jsxs("div",{className:"muted small",style:{marginBottom:8},children:["备用 Provider 库:被「局部阈值」规则的 on_exceeded=fallback 引用。 可填与下方「实际 Provider」不同的标识(人工兜底 ID);实际可调用性以",p.jsx("code",{children:" context.get_provider_by_id "})," 为准。"]}),p.jsx("datalist",{id:a,children:(t||[]).map(o=>p.jsx("option",{value:o.id,children:o.model?`${o.id} (${o.model})`:o.id},o.id))}),p.jsxs("table",{children:[p.jsx("thead",{children:p.jsxs("tr",{children:[p.jsx("th",{style:{width:30}}),p.jsx("th",{children:"Provider ID"}),p.jsx("th",{children:"备注"}),p.jsx("th",{style:{width:40}})]})}),p.jsx("tbody",{children:e.length===0?p.jsx("tr",{children:p.jsx("td",{colSpan:4,className:"muted small",style:{textAlign:"center"},children:"暂无备用 Provider(点击下方「添加」新增)"})}):e.map((o,l)=>p.jsxs("tr",{children:[p.jsx("td",{children:p.jsx("input",{type:"checkbox",checked:o.enabled,onChange:s=>r(l,{enabled:s.target.checked})})}),p.jsx("td",{children:p.jsx("input",{className:"budget-input mono",list:a,value:o.id,onChange:s=>r(l,{id:s.target.value}),placeholder:"从下拉选择或手动输入",style:{width:"100%"}})}),p.jsx("td",{children:p.jsx("input",{className:"budget-input",value:o.note||"",onChange:s=>r(l,{note:s.target.value}),style:{width:"100%"},placeholder:"(可选)"})}),p.jsx("td",{children:p.jsx("button",{type:"button",className:"btn",onClick:()=>n(l),children:"✕"})})]},`${o.id}-${l}`))})]}),p.jsx("div",{style:{marginTop:8},children:p.jsx("button",{type:"button",className:"btn",onClick:()=>i(),children:"+ 添加备用 Provider"})})]})}function Qm({status:e,error:t}){if(e==="idle")return null;const r=e==="saving"?"正在保存…":e==="saved"?"✅ 已保存":`❌ 保存失败:${t||"未知错误"}`;return p.jsx("div",{className:`save-toast save-toast-${e}`,role:"status","aria-live":"polite",children:r})}const sq={umo:"",provider:"",user:""};function uq(e="umo"){return{id:`ov-${Date.now()}-${Math.random().toString(36).slice(2,6)}`,enabled:!0,target_type:e,target_value:sq[e],token_limit:0,cost_limit:0,on_exceeded:"stop",stop_message:"",fallback_provider_ids:[],fallback_token_limit:0,current:{token:{used:0,ratio:0,exceeded:!1},cost:{used:0,ratio:0,exceeded:!1}}}}function cq({refreshNonce:e}){const t=ut(()=>fe.getBudgets(),[e]),r=ut(()=>fe.getProviders(),[e]),n=t.data,[i,a]=A.useState({}),[o,l]=A.useState({}),[s,u]=A.useState({}),[f,c]=A.useState([]),[d,h]=A.useState([]),[v,g]=A.useState("stop"),[w,y]=A.useState(!1);A.useEffect(()=>{n&&(a({...n.limits||{}}),l({...n.limits_cost||{}}),u({...n.limits_cost_currency||{}}),c((n.overrides||[]).map($=>({...$}))),h((n.fallback_providers||[]).map($=>({...$}))),g(n.global_default_on_exceeded||"stop"),y(!0))},[n]);const m=r.data?.providers||[],x=A.useMemo(()=>d.filter($=>$.enabled).map($=>$.id),[d]),S=n?.dimensions||{},b=($,L)=>a(F=>({...F,[$]:Math.max(0,parseInt(L,10)||0)})),_=($,L)=>l(F=>({...F,[$]:Math.max(0,+L||0)})),O=($,L)=>u(F=>({...F,[$]:L})),j=($,L)=>c(F=>F.map((M,I)=>I===$?{...M,...L}:M)),P=($,L)=>c(F=>{const M=F.slice();return L==="up"&&$>0?[M[$-1],M[$]]=[M[$],M[$-1]]:L==="down"&&$c(L=>L.filter((F,M)=>M!==$)),N=()=>c($=>[...$,uq("umo")]),k=($,L)=>h(F=>F.map((M,I)=>I===$?{...M,...L}:M)),R=$=>h(L=>L.filter((F,M)=>M!==$)),C=($="")=>h(L=>[...L,{id:$||"",enabled:!0,note:""}]),D=A.useMemo(()=>({budgets:i,budgets_cost:o,budgets_cost_currency:s,budget_overrides:f.filter($=>$.target_value&&$.target_value.trim()).map(({current:$,id:L,...F})=>F),fallback_providers:d.filter($=>$.id&&$.id.trim()),default_on_exceeded:v}),[i,o,s,f,d,v]),{status:U,error:W}=Ym(D,async $=>{await fe.postSaveConfig($)},{enabled:w});return t.loading&&!n?p.jsx(Xr,{}):t.error?p.jsx(Tn,{message:`加载预算失败:${t.error}`}):p.jsxs("div",{children:[p.jsxs(Ne,{children:[p.jsx("h2",{children:"预算总览(5 维全局默认)"}),p.jsxs("div",{className:"muted small",style:{marginBottom:8},children:["Token 与花费两列均可填写,修改后自动保存。 ",p.jsx("code",{children:" per_*_daily "}),"类维度显示的是本周期消耗最多的代表会话 / 模型,并非该维度的全量聚合(运行时按当前请求实时拦截)。"]}),p.jsx(rq,{limits:i,limitsCost:o,budgetsCostCurrency:s,dimensions:S,onChangeLimit:b,onChangeLimitCost:_,onChangeCostCurrency:O})]}),p.jsxs(Ne,{children:[p.jsx("h2",{children:"局部阈值(优先级高于全局)"}),p.jsx(oq,{overrides:f,providers:m,fallbackProviders:x.map($=>({id:$,enabled:!0})),onChange:j,onMove:P,onDelete:T,onAdd:N})]}),p.jsxs(Ne,{children:[p.jsx("h2",{children:"备用 Provider 库"}),p.jsx(lq,{providers:d,realProviders:m,onChange:k,onDelete:R,onAdd:C})]}),p.jsxs(Ne,{children:[p.jsx("div",{className:"budget-head",children:p.jsx("h2",{children:"全局默认超限处理"})}),p.jsx("div",{className:"muted small",style:{marginBottom:6},children:"当 override 未命中且全局 5 维超限时,按此选项处理。"}),p.jsxs("select",{value:v,onChange:$=>g($.target.value),children:[p.jsx("option",{value:"stop",children:"硬拦截"}),p.jsx("option",{value:"fallback",children:"切换备用 Provider(按备用库顺序)"}),p.jsx("option",{value:"warn",children:"仅警告(不中断)"})]})]}),p.jsx(Qm,{status:U,error:W})]})}function iP({segments:e}){const t=e.reduce((r,n)=>r+(n.value||0),0);return t<=0?p.jsx("div",{className:"empty",children:"暂无组件数据"}):p.jsxs(p.Fragment,{children:[p.jsx("div",{className:"stacked-bar",children:e.map(r=>{const n=Math.round(r.value*100/t);return n<=0?null:p.jsx("div",{className:"stacked-seg",style:{width:`${n}%`,background:r.color},title:r.tooltip?`${r.label} ${n}% +In order to be iterable, non-array objects must have a [Symbol.iterator]() method.`)}function eP(e,t){if(e){if(typeof e=="string")return Uh(e,t);var r=Object.prototype.toString.call(e).slice(8,-1);if(r==="Object"&&e.constructor&&(r=e.constructor.name),r==="Map"||r==="Set")return Array.from(e);if(r==="Arguments"||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(r))return Uh(e,t)}}function kX(e){if(typeof Symbol<"u"&&e[Symbol.iterator]!=null||e["@@iterator"]!=null)return Array.from(e)}function TX(e){if(Array.isArray(e))return Uh(e)}function Uh(e,t){(t==null||t>e.length)&&(t=e.length);for(var r=0,n=new Array(t);r0?o:t&&t.length&&K(i)&&K(a)?t.slice(i,a+1):[]};function nP(e){return e==="number"?[0,"auto"]:void 0}var Wh=function(t,r,n,i){var a=t.graphicalItems,o=t.tooltipAxis,l=Sf(r,t);return n<0||!a||!a.length||n>=l.length?null:a.reduce(function(s,u){var f,c=(f=u.props.data)!==null&&f!==void 0?f:r;c&&t.dataStartIndex+t.dataEndIndex!==0&&t.dataEndIndex-t.dataStartIndex>=n&&(c=c.slice(t.dataStartIndex,t.dataEndIndex+1));var d;if(o.dataKey&&!o.allowDuplicatedCategory){var h=c===void 0?l:c;d=hu(h,o.dataKey,i)}else d=c&&c[n]||l[n];return d?[].concat(ka(s),[qO(u,d)]):s},[])},e1=function(t,r,n,i){var a=i||{x:t.chartX,y:t.chartY},o=IX(a,n),l=t.orderedTooltipTicks,s=t.tooltipAxis,u=t.tooltipTicks,f=L5(o,l,u,s);if(f>=0&&u){var c=u[f]&&u[f].value,d=Wh(t,r,f,c),h=DX(n,l,f,a);return{activeTooltipIndex:f,activeLabel:c,activePayload:d,activeCoordinate:h}}return null},LX=function(t,r){var n=r.axes,i=r.graphicalItems,a=r.axisType,o=r.axisIdKey,l=r.stackGroups,s=r.dataStartIndex,u=r.dataEndIndex,f=t.layout,c=t.children,d=t.stackOffset,h=KO(f,a);return n.reduce(function(v,g){var w,y=g.type.defaultProps!==void 0?z(z({},g.type.defaultProps),g.props):g.props,m=y.type,x=y.dataKey,S=y.allowDataOverflow,b=y.allowDuplicatedCategory,_=y.scale,O=y.ticks,j=y.includeHidden,P=y[o];if(v[P])return v;var T=Sf(t.data,{graphicalItems:i.filter(function(I){var X,J=o in I.props?I.props[o]:(X=I.type.defaultProps)===null||X===void 0?void 0:X[o];return J===P}),dataStartIndex:s,dataEndIndex:u}),N=T.length,k,R,C;uX(y.domain,S,m)&&(k=oh(y.domain,null,S),h&&(m==="number"||_!=="auto")&&(C=Co(T,x,"category")));var D=nP(m);if(!k||k.length===0){var U,W=(U=y.domain)!==null&&U!==void 0?U:D;if(x){if(k=Co(T,x,m),m==="category"&&h){var $=jC(k);b&&$?(R=k,k=rc(0,N)):b||(k=Nb(W,k,g).reduce(function(I,X){return I.indexOf(X)>=0?I:[].concat(ka(I),[X])},[]))}else if(m==="category")b?k=k.filter(function(I){return I!==""&&!oe(I)}):k=Nb(W,k,g).reduce(function(I,X){return I.indexOf(X)>=0||X===""||oe(X)?I:[].concat(ka(I),[X])},[]);else if(m==="number"){var L=U5(T,i.filter(function(I){var X,J,te=o in I.props?I.props[o]:(X=I.type.defaultProps)===null||X===void 0?void 0:X[o],me="hide"in I.props?I.props.hide:(J=I.type.defaultProps)===null||J===void 0?void 0:J.hide;return te===P&&(j||!me)}),x,a,f);L&&(k=L)}h&&(m==="number"||_!=="auto")&&(C=Co(T,x,"category"))}else h?k=rc(0,N):l&&l[P]&&l[P].hasStack&&m==="number"?k=d==="expand"?[0,1]:XO(l[P].stackGroups,s,u):k=VO(T,i.filter(function(I){var X=o in I.props?I.props[o]:I.type.defaultProps[o],J="hide"in I.props?I.props.hide:I.type.defaultProps.hide;return X===P&&(j||!J)}),m,f,!0);if(m==="number")k=zh(c,k,P,a,O),W&&(k=oh(W,k,S));else if(m==="category"&&W){var F=W,M=k.every(function(I){return F.indexOf(I)>=0});M&&(k=F)}}return z(z({},v),{},ee({},P,z(z({},y),{},{axisType:a,domain:k,categoricalDomain:C,duplicateDomain:R,originalDomain:(w=y.domain)!==null&&w!==void 0?w:D,isCategorical:h,layout:f})))},{})},RX=function(t,r){var n=r.graphicalItems,i=r.Axis,a=r.axisType,o=r.axisIdKey,l=r.stackGroups,s=r.dataStartIndex,u=r.dataEndIndex,f=t.layout,c=t.children,d=Sf(t.data,{graphicalItems:n,dataStartIndex:s,dataEndIndex:u}),h=d.length,v=KO(f,a),g=-1;return n.reduce(function(w,y){var m=y.type.defaultProps!==void 0?z(z({},y.type.defaultProps),y.props):y.props,x=m[o],S=nP("number");if(!w[x]){g++;var b;return v?b=rc(0,h):l&&l[x]&&l[x].hasStack?(b=XO(l[x].stackGroups,s,u),b=zh(c,b,x,a)):(b=oh(S,VO(d,n.filter(function(_){var O,j,P=o in _.props?_.props[o]:(O=_.type.defaultProps)===null||O===void 0?void 0:O[o],T="hide"in _.props?_.props.hide:(j=_.type.defaultProps)===null||j===void 0?void 0:j.hide;return P===x&&!T}),"number",f),i.defaultProps.allowDataOverflow),b=zh(c,b,x,a)),z(z({},w),{},ee({},x,z(z({axisType:a},i.defaultProps),{},{hide:!0,orientation:qt(NX,"".concat(a,".").concat(g%2),null),domain:b,originalDomain:S,isCategorical:v,layout:f})))}return w},{})},BX=function(t,r){var n=r.axisType,i=n===void 0?"xAxis":n,a=r.AxisComp,o=r.graphicalItems,l=r.stackGroups,s=r.dataStartIndex,u=r.dataEndIndex,f=t.children,c="".concat(i,"Id"),d=Yt(f,a),h={};return d&&d.length?h=LX(t,{axes:d,graphicalItems:o,axisType:i,axisIdKey:c,stackGroups:l,dataStartIndex:s,dataEndIndex:u}):o&&o.length&&(h=RX(t,{Axis:a,graphicalItems:o,axisType:i,axisIdKey:c,stackGroups:l,dataStartIndex:s,dataEndIndex:u})),h},zX=function(t){var r=fn(t),n=Rr(r,!1,!0);return{tooltipTicks:n,orderedTooltipTicks:cm(n,function(i){return i.coordinate}),tooltipAxis:r,tooltipAxisBandSize:Ku(r,n)}},t1=function(t){var r=t.children,n=t.defaultShowTooltip,i=Nt(r,ga),a=0,o=0;return t.data&&t.data.length!==0&&(o=t.data.length-1),i&&i.props&&(i.props.startIndex>=0&&(a=i.props.startIndex),i.props.endIndex>=0&&(o=i.props.endIndex)),{chartX:0,chartY:0,dataStartIndex:a,dataEndIndex:o,activeTooltipIndex:-1,isTooltipActive:!!n}},FX=function(t){return!t||!t.length?!1:t.some(function(r){var n=zr(r&&r.type);return n&&n.indexOf("Bar")>=0})},r1=function(t){return t==="horizontal"?{numericAxisName:"yAxis",cateAxisName:"xAxis"}:t==="vertical"?{numericAxisName:"xAxis",cateAxisName:"yAxis"}:t==="centric"?{numericAxisName:"radiusAxis",cateAxisName:"angleAxis"}:{numericAxisName:"angleAxis",cateAxisName:"radiusAxis"}},UX=function(t,r){var n=t.props,i=t.graphicalItems,a=t.xAxisMap,o=a===void 0?{}:a,l=t.yAxisMap,s=l===void 0?{}:l,u=n.width,f=n.height,c=n.children,d=n.margin||{},h=Nt(c,ga),v=Nt(c,qi),g=Object.keys(s).reduce(function(b,_){var O=s[_],j=O.orientation;return!O.mirror&&!O.hide?z(z({},b),{},ee({},j,b[j]+O.width)):b},{left:d.left||0,right:d.right||0}),w=Object.keys(o).reduce(function(b,_){var O=o[_],j=O.orientation;return!O.mirror&&!O.hide?z(z({},b),{},ee({},j,qt(b,"".concat(j))+O.height)):b},{top:d.top||0,bottom:d.bottom||0}),y=z(z({},w),g),m=y.bottom;h&&(y.bottom+=h.props.height||ga.defaultProps.height),v&&r&&(y=z5(y,i,n,r));var x=u-y.left-y.right,S=f-y.top-y.bottom;return z(z({brushBottom:m},y),{},{width:Math.max(x,0),height:Math.max(S,0)})},WX=function(t,r){if(r==="xAxis")return t[r].width;if(r==="yAxis")return t[r].height},Xm=function(t){var r=t.chartName,n=t.GraphicalChild,i=t.defaultTooltipEventType,a=i===void 0?"axis":i,o=t.validateTooltipEventTypes,l=o===void 0?["axis"]:o,s=t.axisComponents,u=t.legendContent,f=t.formatAxisMap,c=t.defaultProps,d=function(y,m){var x=m.graphicalItems,S=m.stackGroups,b=m.offset,_=m.updateId,O=m.dataStartIndex,j=m.dataEndIndex,P=y.barSize,T=y.layout,N=y.barGap,k=y.barCategoryGap,R=y.maxBarSize,C=r1(T),D=C.numericAxisName,U=C.cateAxisName,W=FX(x),$=[];return x.forEach(function(L,F){var M=Sf(y.data,{graphicalItems:[L],dataStartIndex:O,dataEndIndex:j}),I=L.type.defaultProps!==void 0?z(z({},L.type.defaultProps),L.props):L.props,X=I.dataKey,J=I.maxBarSize,te=I["".concat(D,"Id")],me=I["".concat(U,"Id")],Re={},ze=s.reduce(function(Rn,Bn){var _f=m["".concat(Bn.axisType,"Map")],Zm=I["".concat(Bn.axisType,"Id")];_f&&_f[Zm]||Bn.axisType==="zAxis"||pi();var Jm=_f[Zm];return z(z({},Rn),{},ee(ee({},Bn.axisType,Jm),"".concat(Bn.axisType,"Ticks"),Rr(Jm)))},Re),q=ze[U],re=ze["".concat(U,"Ticks")],ie=S&&S[te]&&S[te].hasStack&&eU(L,S[te].stackGroups),H=zr(L.type).indexOf("Bar")>=0,je=Ku(q,re),se=[],B=W&&R5({barSize:P,stackGroups:S,totalSize:WX(ze,U)});if(H){var G,Z,ce=oe(J)?R:J,$t=(G=(Z=Ku(q,re,!0))!==null&&Z!==void 0?Z:ce)!==null&&G!==void 0?G:0;se=B5({barGap:N,barCategoryGap:k,bandSize:$t!==je?$t:je,sizeList:B[me],maxBarSize:ce}),$t!==je&&(se=se.map(function(Rn){return z(z({},Rn),{},{position:z(z({},Rn.position),{},{offset:Rn.position.offset-$t/2})})}))}var rn=L&&L.type&&L.type.getComposedData;rn&&$.push({props:z(z({},rn(z(z({},ze),{},{displayedData:M,props:y,dataKey:X,item:L,bandSize:je,barPosition:se,offset:b,stackedData:ie,layout:T,dataStartIndex:O,dataEndIndex:j}))),{},ee(ee(ee({key:L.key||"item-".concat(F)},D,ze[D]),U,ze[U]),"animationId",_)),childIndex:LC(L,y.children),item:L})}),$},h=function(y,m){var x=y.props,S=y.dataStartIndex,b=y.dataEndIndex,_=y.updateId;if(!Ag({props:x}))return null;var O=x.children,j=x.layout,P=x.stackOffset,T=x.data,N=x.reverseStackOrder,k=r1(j),R=k.numericAxisName,C=k.cateAxisName,D=Yt(O,n),U=Q5(T,D,"".concat(R,"Id"),"".concat(C,"Id"),P,N),W=s.reduce(function(I,X){var J="".concat(X.axisType,"Map");return z(z({},I),{},ee({},J,BX(x,z(z({},X),{},{graphicalItems:D,stackGroups:X.axisType===R&&U,dataStartIndex:S,dataEndIndex:b}))))},{}),$=UX(z(z({},W),{},{props:x,graphicalItems:D}),m?.legendBBox);Object.keys(W).forEach(function(I){W[I]=f(x,W[I],$,I.replace("Map",""),r)});var L=W["".concat(C,"Map")],F=zX(L),M=d(x,z(z({},W),{},{dataStartIndex:S,dataEndIndex:b,updateId:_,graphicalItems:D,stackGroups:U,offset:$}));return z(z({formattedGraphicalItems:M,graphicalItems:D,offset:$,stackGroups:U},F),W)},v=function(w){function y(m){var x,S,b;return SX(this,y),b=jX(this,y,[m]),ee(b,"eventEmitterSymbol",Symbol("rechartsEventEmitter")),ee(b,"accessibilityManager",new sX),ee(b,"handleLegendBBoxUpdate",function(_){if(_){var O=b.state,j=O.dataStartIndex,P=O.dataEndIndex,T=O.updateId;b.setState(z({legendBBox:_},h({props:b.props,dataStartIndex:j,dataEndIndex:P,updateId:T},z(z({},b.state),{},{legendBBox:_}))))}}),ee(b,"handleReceiveSyncEvent",function(_,O,j){if(b.props.syncId===_){if(j===b.eventEmitterSymbol&&typeof b.props.syncMethod!="function")return;b.applySyncEvent(O)}}),ee(b,"handleBrushChange",function(_){var O=_.startIndex,j=_.endIndex;if(O!==b.state.dataStartIndex||j!==b.state.dataEndIndex){var P=b.state.updateId;b.setState(function(){return z({dataStartIndex:O,dataEndIndex:j},h({props:b.props,dataStartIndex:O,dataEndIndex:j,updateId:P},b.state))}),b.triggerSyncEvent({dataStartIndex:O,dataEndIndex:j})}}),ee(b,"handleMouseEnter",function(_){var O=b.getMouseInfo(_);if(O){var j=z(z({},O),{},{isTooltipActive:!0});b.setState(j),b.triggerSyncEvent(j);var P=b.props.onMouseEnter;ne(P)&&P(j,_)}}),ee(b,"triggeredAfterMouseMove",function(_){var O=b.getMouseInfo(_),j=O?z(z({},O),{},{isTooltipActive:!0}):{isTooltipActive:!1};b.setState(j),b.triggerSyncEvent(j);var P=b.props.onMouseMove;ne(P)&&P(j,_)}),ee(b,"handleItemMouseEnter",function(_){b.setState(function(){return{isTooltipActive:!0,activeItem:_,activePayload:_.tooltipPayload,activeCoordinate:_.tooltipPosition||{x:_.cx,y:_.cy}}})}),ee(b,"handleItemMouseLeave",function(){b.setState(function(){return{isTooltipActive:!1}})}),ee(b,"handleMouseMove",function(_){_.persist(),b.throttleTriggeredAfterMouseMove(_)}),ee(b,"handleMouseLeave",function(_){b.throttleTriggeredAfterMouseMove.cancel();var O={isTooltipActive:!1};b.setState(O),b.triggerSyncEvent(O);var j=b.props.onMouseLeave;ne(j)&&j(O,_)}),ee(b,"handleOuterEvent",function(_){var O=DC(_),j=qt(b.props,"".concat(O));if(O&&ne(j)){var P,T;/.*touch.*/i.test(O)?T=b.getMouseInfo(_.changedTouches[0]):T=b.getMouseInfo(_),j((P=T)!==null&&P!==void 0?P:{},_)}}),ee(b,"handleClick",function(_){var O=b.getMouseInfo(_);if(O){var j=z(z({},O),{},{isTooltipActive:!0});b.setState(j),b.triggerSyncEvent(j);var P=b.props.onClick;ne(P)&&P(j,_)}}),ee(b,"handleMouseDown",function(_){var O=b.props.onMouseDown;if(ne(O)){var j=b.getMouseInfo(_);O(j,_)}}),ee(b,"handleMouseUp",function(_){var O=b.props.onMouseUp;if(ne(O)){var j=b.getMouseInfo(_);O(j,_)}}),ee(b,"handleTouchMove",function(_){_.changedTouches!=null&&_.changedTouches.length>0&&b.throttleTriggeredAfterMouseMove(_.changedTouches[0])}),ee(b,"handleTouchStart",function(_){_.changedTouches!=null&&_.changedTouches.length>0&&b.handleMouseDown(_.changedTouches[0])}),ee(b,"handleTouchEnd",function(_){_.changedTouches!=null&&_.changedTouches.length>0&&b.handleMouseUp(_.changedTouches[0])}),ee(b,"handleDoubleClick",function(_){var O=b.props.onDoubleClick;if(ne(O)){var j=b.getMouseInfo(_);O(j,_)}}),ee(b,"handleContextMenu",function(_){var O=b.props.onContextMenu;if(ne(O)){var j=b.getMouseInfo(_);O(j,_)}}),ee(b,"triggerSyncEvent",function(_){b.props.syncId!==void 0&&Sd.emit(_d,b.props.syncId,_,b.eventEmitterSymbol)}),ee(b,"applySyncEvent",function(_){var O=b.props,j=O.layout,P=O.syncMethod,T=b.state.updateId,N=_.dataStartIndex,k=_.dataEndIndex;if(_.dataStartIndex!==void 0||_.dataEndIndex!==void 0)b.setState(z({dataStartIndex:N,dataEndIndex:k},h({props:b.props,dataStartIndex:N,dataEndIndex:k,updateId:T},b.state)));else if(_.activeTooltipIndex!==void 0){var R=_.chartX,C=_.chartY,D=_.activeTooltipIndex,U=b.state,W=U.offset,$=U.tooltipTicks;if(!W)return;if(typeof P=="function")D=P($,_);else if(P==="value"){D=-1;for(var L=0;L<$.length;L++)if($[L].value===_.activeLabel){D=L;break}}var F=z(z({},W),{},{x:W.left,y:W.top}),M=Math.min(R,F.x+F.width),I=Math.min(C,F.y+F.height),X=$[D]&&$[D].value,J=Wh(b.state,b.props.data,D),te=$[D]?{x:j==="horizontal"?$[D].coordinate:M,y:j==="horizontal"?I:$[D].coordinate}:rP;b.setState(z(z({},_),{},{activeLabel:X,activeCoordinate:te,activePayload:J,activeTooltipIndex:D}))}else b.setState(_)}),ee(b,"renderCursor",function(_){var O,j=b.state,P=j.isTooltipActive,T=j.activeCoordinate,N=j.activePayload,k=j.offset,R=j.activeTooltipIndex,C=j.tooltipAxisBandSize,D=b.getTooltipEventType(),U=(O=_.props.active)!==null&&O!==void 0?O:P,W=b.props.layout,$=_.key||"_recharts-cursor";return E.createElement(vX,{key:$,activeCoordinate:T,activePayload:N,activeTooltipIndex:R,chartName:r,element:_,isActive:U,layout:W,offset:k,tooltipAxisBandSize:C,tooltipEventType:D})}),ee(b,"renderPolarAxis",function(_,O,j){var P=qt(_,"type.axisType"),T=qt(b.state,"".concat(P,"Map")),N=_.type.defaultProps,k=N!==void 0?z(z({},N),_.props):_.props,R=T&&T[k["".concat(P,"Id")]];return A.cloneElement(_,z(z({},R),{},{className:ue(P,R.className),key:_.key||"".concat(O,"-").concat(j),ticks:Rr(R,!0)}))}),ee(b,"renderPolarGrid",function(_){var O=_.props,j=O.radialLines,P=O.polarAngles,T=O.polarRadius,N=b.state,k=N.radiusAxisMap,R=N.angleAxisMap,C=fn(k),D=fn(R),U=D.cx,W=D.cy,$=D.innerRadius,L=D.outerRadius;return A.cloneElement(_,{polarAngles:Array.isArray(P)?P:Rr(D,!0).map(function(F){return F.coordinate}),polarRadius:Array.isArray(T)?T:Rr(C,!0).map(function(F){return F.coordinate}),cx:U,cy:W,innerRadius:$,outerRadius:L,key:_.key||"polar-grid",radialLines:j})}),ee(b,"renderLegend",function(){var _=b.state.formattedGraphicalItems,O=b.props,j=O.children,P=O.width,T=O.height,N=b.props.margin||{},k=P-(N.left||0)-(N.right||0),R=WO({children:j,formattedGraphicalItems:_,legendWidth:k,legendContent:u});if(!R)return null;var C=R.item,D=Zx(R,mX);return A.cloneElement(C,z(z({},D),{},{chartWidth:P,chartHeight:T,margin:N,onBBoxUpdate:b.handleLegendBBoxUpdate}))}),ee(b,"renderTooltip",function(){var _,O=b.props,j=O.children,P=O.accessibilityLayer,T=Nt(j,wt);if(!T)return null;var N=b.state,k=N.isTooltipActive,R=N.activeCoordinate,C=N.activePayload,D=N.activeLabel,U=N.offset,W=(_=T.props.active)!==null&&_!==void 0?_:k;return A.cloneElement(T,{viewBox:z(z({},U),{},{x:U.left,y:U.top}),active:W,label:D,payload:W?C:[],coordinate:R,accessibilityLayer:P})}),ee(b,"renderBrush",function(_){var O=b.props,j=O.margin,P=O.data,T=b.state,N=T.offset,k=T.dataStartIndex,R=T.dataEndIndex,C=T.updateId;return A.cloneElement(_,{key:_.key||"_recharts-brush",onChange:xs(b.handleBrushChange,_.props.onChange),data:P,x:K(_.props.x)?_.props.x:N.left,y:K(_.props.y)?_.props.y:N.top+N.height+N.brushBottom-(j.bottom||0),width:K(_.props.width)?_.props.width:N.width,startIndex:k,endIndex:R,updateId:"brush-".concat(C)})}),ee(b,"renderReferenceElement",function(_,O,j){if(!_)return null;var P=b,T=P.clipPathId,N=b.state,k=N.xAxisMap,R=N.yAxisMap,C=N.offset,D=_.type.defaultProps||{},U=_.props,W=U.xAxisId,$=W===void 0?D.xAxisId:W,L=U.yAxisId,F=L===void 0?D.yAxisId:L;return A.cloneElement(_,{key:_.key||"".concat(O,"-").concat(j),xAxis:k[$],yAxis:R[F],viewBox:{x:C.left,y:C.top,width:C.width,height:C.height},clipPathId:T})}),ee(b,"renderActivePoints",function(_){var O=_.item,j=_.activePoint,P=_.basePoint,T=_.childIndex,N=_.isRange,k=[],R=O.props.key,C=O.item.type.defaultProps!==void 0?z(z({},O.item.type.defaultProps),O.item.props):O.item.props,D=C.activeDot,U=C.dataKey,W=z(z({index:T,dataKey:U,cx:j.x,cy:j.y,r:4,fill:Dm(O.item),strokeWidth:2,stroke:"#fff",payload:j.payload,value:j.value},ae(D,!1)),vu(D));return k.push(y.renderActiveDot(D,W,"".concat(R,"-activePoint-").concat(T))),P?k.push(y.renderActiveDot(D,z(z({},W),{},{cx:P.x,cy:P.y}),"".concat(R,"-basePoint-").concat(T))):N&&k.push(null),k}),ee(b,"renderGraphicChild",function(_,O,j){var P=b.filterFormatItem(_,O,j);if(!P)return null;var T=b.getTooltipEventType(),N=b.state,k=N.isTooltipActive,R=N.tooltipAxis,C=N.activeTooltipIndex,D=N.activeLabel,U=b.props.children,W=Nt(U,wt),$=P.props,L=$.points,F=$.isRange,M=$.baseLine,I=P.item.type.defaultProps!==void 0?z(z({},P.item.type.defaultProps),P.item.props):P.item.props,X=I.activeDot,J=I.hide,te=I.activeBar,me=I.activeShape,Re=!!(!J&&k&&W&&(X||te||me)),ze={};T!=="axis"&&W&&W.props.trigger==="click"?ze={onClick:xs(b.handleItemMouseEnter,_.props.onClick)}:T!=="axis"&&(ze={onMouseLeave:xs(b.handleItemMouseLeave,_.props.onMouseLeave),onMouseEnter:xs(b.handleItemMouseEnter,_.props.onMouseEnter)});var q=A.cloneElement(_,z(z({},P.props),ze));function re(Bn){return typeof R.dataKey=="function"?R.dataKey(Bn.payload):null}if(Re)if(C>=0){var ie,H;if(R.dataKey&&!R.allowDuplicatedCategory){var je=typeof R.dataKey=="function"?re:"payload.".concat(R.dataKey.toString());ie=hu(L,je,D),H=F&&M&&hu(M,je,D)}else ie=L?.[C],H=F&&M&&M[C];if(me||te){var se=_.props.activeIndex!==void 0?_.props.activeIndex:C;return[A.cloneElement(_,z(z(z({},P.props),ze),{},{activeIndex:se})),null,null]}if(!oe(ie))return[q].concat(ka(b.renderActivePoints({item:P,activePoint:ie,basePoint:H,childIndex:C,isRange:F})))}else{var B,G=(B=b.getItemByXY(b.state.activeCoordinate))!==null&&B!==void 0?B:{graphicalItem:q},Z=G.graphicalItem,ce=Z.item,$t=ce===void 0?_:ce,rn=Z.childIndex,Rn=z(z(z({},P.props),ze),{},{activeIndex:rn});return[A.cloneElement($t,Rn),null,null]}return F?[q,null,null]:[q,null]}),ee(b,"renderCustomized",function(_,O,j){return A.cloneElement(_,z(z({key:"recharts-customized-".concat(j)},b.props),b.state))}),ee(b,"renderMap",{CartesianGrid:{handler:js,once:!0},ReferenceArea:{handler:b.renderReferenceElement},ReferenceLine:{handler:js},ReferenceDot:{handler:b.renderReferenceElement},XAxis:{handler:js},YAxis:{handler:js},Brush:{handler:b.renderBrush,once:!0},Bar:{handler:b.renderGraphicChild},Line:{handler:b.renderGraphicChild},Area:{handler:b.renderGraphicChild},Radar:{handler:b.renderGraphicChild},RadialBar:{handler:b.renderGraphicChild},Scatter:{handler:b.renderGraphicChild},Pie:{handler:b.renderGraphicChild},Funnel:{handler:b.renderGraphicChild},Tooltip:{handler:b.renderCursor,once:!0},PolarGrid:{handler:b.renderPolarGrid,once:!0},PolarAngleAxis:{handler:b.renderPolarAxis},PolarRadiusAxis:{handler:b.renderPolarAxis},Customized:{handler:b.renderCustomized}}),b.clipPathId="".concat((x=m.id)!==null&&x!==void 0?x:za("recharts"),"-clip"),b.throttleTriggeredAfterMouseMove=W_(b.triggeredAfterMouseMove,(S=m.throttleDelay)!==null&&S!==void 0?S:1e3/60),b.state={},b}return EX(y,w),OX(y,[{key:"componentDidMount",value:function(){var x,S;this.addListener(),this.accessibilityManager.setDetails({container:this.container,offset:{left:(x=this.props.margin.left)!==null&&x!==void 0?x:0,top:(S=this.props.margin.top)!==null&&S!==void 0?S:0},coordinateList:this.state.tooltipTicks,mouseHandlerCallback:this.triggeredAfterMouseMove,layout:this.props.layout}),this.displayDefaultTooltip()}},{key:"displayDefaultTooltip",value:function(){var x=this.props,S=x.children,b=x.data,_=x.height,O=x.layout,j=Nt(S,wt);if(j){var P=j.props.defaultIndex;if(!(typeof P!="number"||P<0||P>this.state.tooltipTicks.length-1)){var T=this.state.tooltipTicks[P]&&this.state.tooltipTicks[P].value,N=Wh(this.state,b,P,T),k=this.state.tooltipTicks[P].coordinate,R=(this.state.offset.top+_)/2,C=O==="horizontal",D=C?{x:k,y:R}:{y:k,x:R},U=this.state.formattedGraphicalItems.find(function($){var L=$.item;return L.type.name==="Scatter"});U&&(D=z(z({},D),U.props.points[P].tooltipPosition),N=U.props.points[P].tooltipPayload);var W={activeTooltipIndex:P,isTooltipActive:!0,activeLabel:T,activePayload:N,activeCoordinate:D};this.setState(W),this.renderCursor(j),this.accessibilityManager.setIndex(P)}}}},{key:"getSnapshotBeforeUpdate",value:function(x,S){if(!this.props.accessibilityLayer)return null;if(this.state.tooltipTicks!==S.tooltipTicks&&this.accessibilityManager.setDetails({coordinateList:this.state.tooltipTicks}),this.props.layout!==x.layout&&this.accessibilityManager.setDetails({layout:this.props.layout}),this.props.margin!==x.margin){var b,_;this.accessibilityManager.setDetails({offset:{left:(b=this.props.margin.left)!==null&&b!==void 0?b:0,top:(_=this.props.margin.top)!==null&&_!==void 0?_:0}})}return null}},{key:"componentDidUpdate",value:function(x){wp([Nt(x.children,wt)],[Nt(this.props.children,wt)])||this.displayDefaultTooltip()}},{key:"componentWillUnmount",value:function(){this.removeListener(),this.throttleTriggeredAfterMouseMove.cancel()}},{key:"getTooltipEventType",value:function(){var x=Nt(this.props.children,wt);if(x&&typeof x.props.shared=="boolean"){var S=x.props.shared?"axis":"item";return l.indexOf(S)>=0?S:a}return a}},{key:"getMouseInfo",value:function(x){if(!this.container)return null;var S=this.container,b=S.getBoundingClientRect(),_=uF(b),O={chartX:Math.round(x.pageX-_.left),chartY:Math.round(x.pageY-_.top)},j=b.width/S.offsetWidth||1,P=this.inRange(O.chartX,O.chartY,j);if(!P)return null;var T=this.state,N=T.xAxisMap,k=T.yAxisMap,R=this.getTooltipEventType(),C=e1(this.state,this.props.data,this.props.layout,P);if(R!=="axis"&&N&&k){var D=fn(N).scale,U=fn(k).scale,W=D&&D.invert?D.invert(O.chartX):null,$=U&&U.invert?U.invert(O.chartY):null;return z(z({},O),{},{xValue:W,yValue:$},C)}return C?z(z({},O),C):null}},{key:"inRange",value:function(x,S){var b=arguments.length>2&&arguments[2]!==void 0?arguments[2]:1,_=this.props.layout,O=x/b,j=S/b;if(_==="horizontal"||_==="vertical"){var P=this.state.offset,T=O>=P.left&&O<=P.left+P.width&&j>=P.top&&j<=P.top+P.height;return T?{x:O,y:j}:null}var N=this.state,k=N.angleAxisMap,R=N.radiusAxisMap;if(k&&R){var C=fn(k);return Db({x:O,y:j},C)}return null}},{key:"parseEventsOfWrapper",value:function(){var x=this.props.children,S=this.getTooltipEventType(),b=Nt(x,wt),_={};b&&S==="axis"&&(b.props.trigger==="click"?_={onClick:this.handleClick}:_={onMouseEnter:this.handleMouseEnter,onDoubleClick:this.handleDoubleClick,onMouseMove:this.handleMouseMove,onMouseLeave:this.handleMouseLeave,onTouchMove:this.handleTouchMove,onTouchStart:this.handleTouchStart,onTouchEnd:this.handleTouchEnd,onContextMenu:this.handleContextMenu});var O=vu(this.props,this.handleOuterEvent);return z(z({},O),_)}},{key:"addListener",value:function(){Sd.on(_d,this.handleReceiveSyncEvent)}},{key:"removeListener",value:function(){Sd.removeListener(_d,this.handleReceiveSyncEvent)}},{key:"filterFormatItem",value:function(x,S,b){for(var _=this.state.formattedGraphicalItems,O=0,j=_.length;O({bucket:i.bucket,count:i.count,tokens:(i.token_input_other||0)+(i.token_input_cached||0)+(i.token_output||0)})),n={fill:t.dim,fontSize:10};return p.jsx(Ul,{width:"100%",height:"100%",children:p.jsxs(HX,{data:r,margin:{top:8,right:8,bottom:0,left:0},children:[p.jsx(Xa,{stroke:t.border,strokeDasharray:"3 3",vertical:!1}),p.jsx(kr,{dataKey:"bucket",tickFormatter:i=>gp(String(i)),tick:n,stroke:t.border}),p.jsx(vr,{yAxisId:"y",tickFormatter:i=>Jo(Number(i)),tick:n,stroke:t.border}),p.jsx(vr,{yAxisId:"y1",orientation:"right",tickFormatter:i=>Jo(Number(i)),tick:n,stroke:t.border}),p.jsx(wt,{}),p.jsx(ja,{yAxisId:"y",type:"monotone",dataKey:"count",name:"调用",stroke:t.accent,dot:!1}),p.jsx(ja,{yAxisId:"y1",type:"monotone",dataKey:"tokens",name:"Token",stroke:t.warn,dot:!1})]})})}function GX({data:e,colors:t}){const r=Or(du()),n={fill:t.dim,fontSize:10};return p.jsx(Ul,{width:"100%",height:"100%",children:p.jsxs(VX,{data:e,margin:{top:8,right:8,bottom:0,left:0},children:[p.jsx("defs",{children:p.jsxs("linearGradient",{id:"costGradient",x1:"0",y1:"0",x2:"0",y2:"1",children:[p.jsx("stop",{offset:"0%",stopColor:t.accent,stopOpacity:.35}),p.jsx("stop",{offset:"100%",stopColor:t.accent,stopOpacity:.02})]})}),p.jsx(Xa,{stroke:t.border,strokeDasharray:"3 3",vertical:!1}),p.jsx(kr,{dataKey:"bucket",tickFormatter:i=>gp(String(i)),tick:n,stroke:t.border}),p.jsx(vr,{tickFormatter:i=>r+Jo(Number(i)),tick:n,stroke:t.border}),p.jsx(wt,{formatter:i=>[XX(i,r),"成本"],labelFormatter:i=>gp(String(i))}),p.jsx(Ln,{type:"monotone",dataKey:"cost",name:"成本",stroke:t.accent,strokeWidth:2,fill:"url(#costGradient)",dot:!1})]})})}function XX(e,t){const r=Number(e??0);return Number.isFinite(r)?t+r.toFixed(4):t+"0"}function n1({data:e,colors:t}){const r={fill:t.dim,fontSize:10};return p.jsx(Ul,{width:"100%",height:"100%",children:p.jsxs(qm,{data:e,layout:"vertical",margin:{top:4,right:16,bottom:4,left:8},children:[p.jsx(Xa,{stroke:t.border,strokeDasharray:"3 3",horizontal:!1}),p.jsx(kr,{type:"number",tickFormatter:n=>"$"+Jo(Number(n)),tick:r,stroke:t.border}),p.jsx(vr,{type:"category",dataKey:"model",width:120,tick:r,stroke:t.border}),p.jsx(wt,{}),p.jsx(pr,{dataKey:"cost",name:"成本",fill:t.accent,radius:[0,3,3,0]})]})})}function qX({data:e,colors:t}){const r={fill:t.dim,fontSize:11},n=e.map(i=>{const a=i.other+i.cached+i.output;return a<=0?{label:i.label,other:0,cached:0,output:0}:{label:i.label,other:+(i.other/a*100).toFixed(1),cached:+(i.cached/a*100).toFixed(1),output:+(i.output/a*100).toFixed(1)}});return p.jsx(Ul,{width:"100%",height:"100%",children:p.jsxs(qm,{data:n,margin:{top:4,right:8,bottom:0,left:0},children:[p.jsx(Xa,{stroke:t.border,strokeDasharray:"3 3",vertical:!1}),p.jsx(kr,{dataKey:"label",tick:r,stroke:t.border}),p.jsx(vr,{tickFormatter:i=>`${i}%`,domain:[0,100],tick:{fill:t.dim,fontSize:10},stroke:t.border}),p.jsx(wt,{formatter:(i,a)=>[`${i}%`,a]}),p.jsx(pr,{dataKey:"other",name:"输入(非缓存)",stackId:"a",fill:t.other}),p.jsx(pr,{dataKey:"cached",name:"缓存命中",stackId:"a",fill:t.cached}),p.jsx(pr,{dataKey:"output",name:"输出",stackId:"a",fill:t.accent})]})})}function YX({data:e,colors:t}){const r={fill:t.dim,fontSize:10};return p.jsx(Ul,{width:"100%",height:"100%",children:p.jsxs(qm,{data:e,layout:"vertical",margin:{top:4,right:16,bottom:4,left:8},children:[p.jsx(Xa,{stroke:t.border,strokeDasharray:"3 3",horizontal:!1}),p.jsx(kr,{type:"number",tickFormatter:n=>Jo(Number(n)),tick:r,stroke:t.border}),p.jsx(vr,{type:"category",dataKey:"umo",width:120,tick:r,stroke:t.border}),p.jsx(wt,{}),p.jsx(pr,{dataKey:"tokens",name:"Token",fill:t.warn,radius:[0,3,3,0]})]})})}function QX({window:e,refreshNonce:t,colors:r,onNavigate:n}){const i=cg(e),a=ut(()=>fe.getOverview(e),[e,t]),o=ut(()=>fe.getTimeline(cg(e),"day"),[e,t]),l=ut(()=>fe.getCompare(e),[e,t]),s=ut(()=>fe.getAlerts(e),[e,t]),u=ut(()=>fe.getOverview("daily"),[t]),f=ut(()=>fe.getOverview("weekly"),[t]),c=ut(()=>fe.getOverview("monthly"),[t]);if(BE(()=>{a.refetch(),o.refetch(),l.refetch(),s.refetch()},3e4,!0),a.loading&&!a.data)return p.jsx(Xr,{});if(a.error)return p.jsx(Tn,{message:`加载总览失败:${a.error}`});const d=a.data,h=d?.usage||{},v=zE(e),g=[{label:"成本",value:hn(d?.cost),sub:`${du()} · ${v}`,delta:p.jsx(fg,{cmp:l.data??null,field:"cost"})},{label:"调用次数",value:pe(h.count),sub:v,delta:p.jsx(fg,{cmp:l.data??null,field:"count"})},{label:"平均缓存命中率",value:`${d?.cache_hit_rate||0}%`,sub:`${d?.cache_samples||0} 样本`},{label:"平均上下文注入",value:pe(d?.avg_injection),sub:`${d?.injection_samples||0} 样本 · token`}],w=(d?.cost_by_model||[]).slice(0,8).map(j=>({model:PS(j.model),cost:j.cost})),y=[{label:"1天",other:u.data?.usage?.token_input_other||0,cached:u.data?.usage?.token_input_cached||0,output:u.data?.usage?.token_output||0},{label:"7天",other:f.data?.usage?.token_input_other||0,cached:f.data?.usage?.token_input_cached||0,output:f.data?.usage?.token_output||0},{label:"30天",other:c.data?.usage?.token_input_other||0,cached:c.data?.usage?.token_input_cached||0,output:c.data?.usage?.token_output||0}],m=y.some(j=>j.other+j.cached+j.output>0),x=(d?.top_sessions||[]).slice(0,8).reverse().map(j=>({umo:pu(j.umo),tokens:j.tokens,cost:j.cost||0})),S=(d?.top_sessions_by_cost||[]).slice(0,8).map(j=>({model:pu(j.umo),cost:j.cost||0})),b=o.data?.series??[],_=o.data?.cost_series??[],O=s.data||[];return p.jsxs("div",{children:[O.length>0&&n&&p.jsx(UE,{alerts:O,onNavigate:n}),p.jsx(zv,{items:g}),p.jsx(KE,{}),p.jsxs("div",{className:"grid-2",children:[p.jsx(Ne,{title:`用量趋势(近 ${i} 天)`,children:b.length?p.jsx("div",{className:"chart-box",children:p.jsx(KX,{series:b,colors:r})}):p.jsx(Vt,{text:"暂无时序数据"})}),p.jsx(Ne,{title:`成本趋势(近 ${i} 天)`,children:_.length?p.jsx("div",{className:"chart-box",children:p.jsx(GX,{data:_,colors:r})}):p.jsx(Vt,{text:"暂无成本时序数据"})})]}),p.jsxs("div",{className:"grid-2",children:[p.jsx(Ne,{title:"按模型成本",children:w.length?p.jsx("div",{className:"chart-box",children:p.jsx(n1,{data:w,colors:r})}):p.jsx(Vt,{text:"暂无模型成本数据"})}),p.jsx(Ne,{title:"Top 会话(按成本)",children:S.length?p.jsx("div",{className:"chart-box",children:p.jsx(n1,{data:S,colors:r})}):p.jsx(Vt,{text:"暂无会话数据"})})]}),p.jsxs("div",{className:"grid-2",children:[p.jsx(Ne,{title:"Top 会话(按 token)",children:x.length?p.jsx("div",{className:"chart-box",children:p.jsx(YX,{data:x,colors:r})}):p.jsx(Vt,{text:"暂无会话数据"})}),p.jsx(Ne,{title:"Token 构成",children:m?p.jsx("div",{className:"chart-box",children:p.jsx(qX,{data:y,colors:r})}):p.jsx(Vt,{text:"暂无 token 数据"})})]})]})}function yc({ratio:e,warnAt:t=80,badAt:r=100,children:n}){const i=Math.min(100,Math.max(0,e||0)),a=i>=r?"bad":i>=t?"warn":"";return p.jsxs("div",{className:"row",style:{alignItems:"center",gap:8},children:[p.jsx("div",{className:"bar-wrap",style:{flex:1},children:p.jsx("div",{className:`bar ${a}`,style:{width:`${i}%`}})}),n!=null&&p.jsx("span",{children:n})]})}const ZX={preset:"7d",start:"",end:"",model:"",umo:"",provider:"",order_by:"created_at",order_dir:"desc"},ho=50;function JX(e){const t=new Date,r=t.toISOString().slice(0,10);if(e.preset==="today")return{start:r,end:r};if(e.preset==="7d"){const n=new Date(t);return n.setDate(n.getDate()-6),{start:n.toISOString().slice(0,10),end:r}}if(e.preset==="30d"){const n=new Date(t);return n.setDate(n.getDate()-29),{start:n.toISOString().slice(0,10),end:r}}return{start:e.start||"",end:e.end||""}}function eq({refreshNonce:e}){const[t,r]=A.useState(ZX),[n,i]=A.useState("model"),[a,o]=A.useState(1),l=A.useMemo(()=>JX(t),[t.preset,t.start,t.end]),u=(ut(()=>fe.getOverview("daily"),[e]).data?.cost_by_model||[]).map(S=>S.model),f=ut(()=>fe.getRecordsAggregate({by:n,umo:t.umo,provider:t.provider,model:t.model,start:l.start,end:l.end}),[n,t.umo,t.provider,t.model,l.start,l.end,e]),c=ut(()=>fe.getRecords({umo:t.umo,provider:t.provider,model:t.model,start:l.start,end:l.end,order_by:t.order_by,order_dir:t.order_dir,limit:1e3}),[t.umo,t.provider,t.model,l.start,l.end,t.order_by,t.order_dir,e]),d=S=>{r(b=>({...b,...S})),o(1)},h=f.data?.groups||[],v=c.data||[],g=h.reduce((S,b)=>S+(b.cost||0),0),w=v.reduce((S,b)=>(S.input+=b.token_input_other||0,S.cached+=b.token_input_cached||0,S.output+=b.token_output||0,S.creation+=b.cache_creation||0,S.cost+=b.cost||0,S),{input:0,cached:0,output:0,creation:0,cost:0}),y=Math.max(1,Math.ceil(v.length/ho)),m=Math.min(a,y),x=v.slice((m-1)*ho,m*ho);return p.jsxs("div",{children:[p.jsxs("div",{className:"toolbar records-toolbar",children:[p.jsx(fu,{value:t.preset,onChange:S=>d({preset:S}),options:[{value:"today",label:"今日"},{value:"7d",label:"7日"},{value:"30d",label:"30日"},{value:"custom",label:"自定义"}]}),t.preset==="custom"&&p.jsxs("span",{className:"custom-range",children:[p.jsx("input",{type:"date",value:t.start,onChange:S=>d({start:S.target.value,preset:"custom"})})," ~ ",p.jsx("input",{type:"date",value:t.end,onChange:S=>d({end:S.target.value,preset:"custom"})})]}),p.jsxs("select",{value:t.model,onChange:S=>d({model:S.target.value}),children:[p.jsx("option",{value:"",children:"全部模型"}),u.map(S=>p.jsx("option",{value:S,children:S},S))]}),p.jsx("input",{defaultValue:t.umo,placeholder:"按会话 UMO 筛选",onBlur:S=>d({umo:S.target.value.trim()}),onKeyDown:S=>{S.key==="Enter"&&d({umo:S.target.value.trim()})}}),p.jsx("input",{defaultValue:t.provider,placeholder:"Provider ID",onBlur:S=>d({provider:S.target.value.trim()}),onKeyDown:S=>{S.key==="Enter"&&d({provider:S.target.value.trim()})}}),p.jsxs("select",{value:t.order_by,onChange:S=>d({order_by:S.target.value}),children:[p.jsx("option",{value:"created_at",children:"按时间"}),p.jsx("option",{value:"token_input_other",children:"按输入"}),p.jsx("option",{value:"token_output",children:"按输出"})]}),p.jsx("button",{className:"btn",title:"升降序",onClick:()=>d({order_dir:t.order_dir==="desc"?"asc":"desc"}),children:t.order_dir==="desc"?"↓":"↑"})]}),p.jsxs(Ne,{className:"agg-panel",children:[p.jsxs("div",{className:"agg-head",children:[p.jsx("h2",{style:{margin:0},children:"交叉聚合"}),p.jsx(fu,{variant:"weak",value:n,onChange:S=>i(S),options:[{value:"model",label:"按模型"},{value:"umo",label:"按会话"}]})]}),f.loading&&!f.data?p.jsx(Xr,{message:"加载聚合…"}):f.error?p.jsxs("div",{className:"muted",children:["聚合失败:",f.error]}):h.length===0?p.jsx(Vt,{text:"暂无聚合数据"}):p.jsxs("table",{children:[p.jsx("thead",{children:p.jsxs("tr",{children:[p.jsx("th",{children:n==="model"?"模型":"会话"}),p.jsx("th",{children:"调用"}),p.jsx("th",{children:"token 合计"}),p.jsx("th",{children:"成本"}),p.jsx("th",{children:"token 占比"}),p.jsx("th",{children:"费用占比"})]})}),p.jsx("tbody",{children:h.map(S=>{const b=g?Math.round(S.cost*1e3/g)/10:0;return p.jsxs("tr",{children:[p.jsx("td",{className:"mono",children:n==="model"?PS(S.key):pu(S.key)}),p.jsx("td",{children:pe(S.count)}),p.jsx("td",{children:pe(S.tokens)}),p.jsx("td",{children:hn(S.cost)}),p.jsx("td",{style:{minWidth:120},children:p.jsxs(yc,{ratio:S.pct,warnAt:25,badAt:50,children:[S.pct,"%"]})}),p.jsx("td",{style:{minWidth:120},children:p.jsxs(yc,{ratio:b,warnAt:25,badAt:50,children:[b,"%"]})})]},S.key)})})]})]}),p.jsxs(Ne,{children:[c.loading&&!c.data?p.jsx(Xr,{}):c.error?p.jsx(Tn,{message:`加载失败:${c.error}`}):v.length===0?p.jsx(Vt,{text:"暂无明细记录"}):p.jsxs("table",{children:[p.jsx("thead",{children:p.jsxs("tr",{children:[p.jsx("th",{children:"时间"}),p.jsx("th",{children:"会话"}),p.jsx("th",{children:"模型"}),p.jsx("th",{children:"Provider"}),p.jsx("th",{children:"输入"}),p.jsx("th",{children:"缓存"}),p.jsx("th",{children:"输出"}),p.jsx("th",{children:"cache写入"}),p.jsx("th",{children:"注入"}),p.jsx("th",{children:"成本"})]})}),p.jsx("tbody",{children:x.map((S,b)=>p.jsxs("tr",{children:[p.jsx("td",{children:Bv(S.created_at)}),p.jsx("td",{className:"mono",title:S.umo||"",children:pu(S.umo)}),p.jsx("td",{className:"mono",title:S.provider_model||"",children:S.provider_model||"-"}),p.jsx("td",{className:"mono",children:S.provider_id||"-"}),p.jsx("td",{children:pe(S.token_input_other)}),p.jsx("td",{children:pe(S.token_input_cached)}),p.jsx("td",{children:pe(S.token_output)}),p.jsx("td",{children:pe(S.cache_creation)}),p.jsx("td",{children:S.injection_total==null?"-":pe(S.injection_total)}),p.jsx("td",{children:S.cost_original!=null&&S.currency_symbol?hn(S.cost_original,S.currency_symbol):hn(S.cost)})]},b))}),p.jsx("tfoot",{children:p.jsxs("tr",{className:"sum-row",children:[p.jsxs("td",{colSpan:4,children:["合计(",v.length," 条)"]}),p.jsx("td",{children:pe(w.input)}),p.jsx("td",{children:pe(w.cached)}),p.jsx("td",{children:pe(w.output)}),p.jsx("td",{children:pe(w.creation)}),p.jsx("td",{}),p.jsx("td",{children:hn(w.cost)})]})})]}),v.length>ho&&p.jsxs("div",{className:"pager",children:[p.jsxs("div",{className:"pager-nav",children:[p.jsx("button",{type:"button",className:"btn",disabled:m<=1,onClick:()=>o(S=>Math.max(1,S-1)),children:"‹ 上一页"}),p.jsx("select",{className:"pager-jump",value:m,onChange:S=>o(+S.target.value),children:Array.from({length:y},(S,b)=>p.jsxs("option",{value:b+1,children:["第 ",b+1," / ",y," 页"]},b))}),p.jsx("button",{type:"button",className:"btn",disabled:m>=y,onClick:()=>o(S=>Math.min(y,S+1)),children:"下一页 ›"})]}),p.jsxs("span",{className:"muted small",children:["共 ",v.length," 条 · 每页 ",ho," 条",v.length>=1e3?"(仅最近 1000 条)":""]})]})]})]})}function Ym(e,t,r={}){const{delay:n=800,toastMs:i=1500,enabled:a=!0}=r,o=A.useMemo(()=>JSON.stringify(e),[e]),l=A.useRef(o);l.current=o;const s=A.useRef(e);s.current=e;const u=A.useRef(t);u.current=t;const[f,c]=A.useState("idle"),[d,h]=A.useState(void 0),v=A.useRef(null),g=A.useRef(!1),w=A.useRef(null),y=A.useRef(null),m=A.useRef(!1),x=A.useCallback(()=>{y.current!==null&&(clearTimeout(y.current),y.current=null)},[]),S=A.useCallback(async()=>{if(!(v.current!==null&&l.current===v.current)){if(m.current){w.current=setTimeout(()=>{S()},n);return}m.current=!0,c("saving");try{await u.current(s.current),v.current=l.current,c("saved"),h(void 0),x(),y.current=setTimeout(()=>c("idle"),i)}catch(_){c("error"),h(_ instanceof Error?_.message:String(_)),x(),y.current=setTimeout(()=>c("idle"),i*3)}finally{m.current=!1}}},[n,i,x]);A.useLayoutEffect(()=>{a&&!g.current&&(v.current=l.current),g.current=a},[a]),A.useEffect(()=>{if(a&&v.current!==null&&o!==v.current)return w.current!==null&&clearTimeout(w.current),w.current=setTimeout(()=>{S()},n),()=>{w.current!==null&&(clearTimeout(w.current),w.current=null)}},[o,a,n,S]),A.useEffect(()=>()=>{w.current!==null&&clearTimeout(w.current),x(),a&&v.current!==null&&!m.current&&l.current!==v.current&&u.current(s.current).catch(()=>{})},[]);const b=A.useCallback(async()=>{w.current!==null&&(clearTimeout(w.current),w.current=null),a&&v.current!==null&&!m.current&&l.current!==v.current&&await S()},[a,S]);return{status:f,error:d,flush:b}}const tq=[{key:"global_daily",label:"全局每日"},{key:"global_monthly",label:"全局每月"},{key:"per_session_daily",label:"单会话每日",note:"代表值"},{key:"per_user_daily",label:"单用户每日",note:"代表值"},{key:"per_model_daily",label:"单模型每日",note:"代表值"}];function rq({limits:e,limitsCost:t,budgetsCostCurrency:r,dimensions:n,onChangeLimit:i,onChangeLimitCost:a,onChangeCostCurrency:o}){return p.jsxs("table",{className:"budget-table",children:[p.jsx("thead",{children:p.jsxs("tr",{children:[p.jsx("th",{style:{minWidth:120},children:"维度"}),p.jsx("th",{style:{width:"42%"},children:"Token(限额 / 消耗)"}),p.jsx("th",{style:{width:"42%"},children:"花费 $(限额 / 消耗)"})]})}),p.jsx("tbody",{children:tq.map(l=>{const s=n[l.key]||{},u=s.token||{limit:0,used:0,ratio:0,exceeded:!1},f=s.cost||{limit:0,used:0,ratio:0,exceeded:!1},c=!!(u.exceeded||f.exceeded);return p.jsxs("tr",{className:c?"exceeded":"",children:[p.jsxs("td",{children:[p.jsx("div",{className:"budget-dim-label",children:l.label}),l.note&&p.jsx("div",{className:"muted small",children:l.note})]}),p.jsx("td",{children:p.jsxs("div",{className:"budget-cell",children:[p.jsx("input",{type:"number",min:"0",className:"budget-input",value:e[l.key]||0,onChange:d=>i(l.key,d.target.value),style:{width:110}}),p.jsxs("div",{className:"muted small budget-cell-used",children:[pe(u.used)," / ",pe(e[l.key]||0)]}),u.limit>0?p.jsxs(yc,{ratio:u.ratio,children:[u.ratio||0,"%"]}):p.jsx("div",{className:"muted small",children:"未设上限"}),u.top_key&&p.jsx("div",{className:"muted small",children:u.top_key})]})}),p.jsx("td",{children:p.jsxs("div",{className:"budget-cell",children:[p.jsx("span",{className:"muted small",children:Or(r[l.key]||"")}),p.jsx("input",{type:"number",min:"0",step:"0.01",className:"budget-input",value:t[l.key]||0,onChange:d=>a(l.key,d.target.value),style:{width:110}}),p.jsxs("select",{className:"budget-input",value:r[l.key]||"",onChange:d=>o(l.key,d.target.value),title:"花费货币(留空=主货币)",style:{width:96},children:[p.jsx("option",{value:"",children:"主货币"}),Ic.map(d=>p.jsxs("option",{value:d,children:[d," (",Or(d),")"]},d))]}),p.jsxs("div",{className:"muted small budget-cell-used",children:[hn(f.used)," / ",hn(f.limit)]}),f.limit>0?p.jsxs(yc,{ratio:f.ratio,children:[f.ratio||0,"%"]}):p.jsx("div",{className:"muted small",children:"未设上限"}),f.top_key&&p.jsx("div",{className:"muted small",children:f.top_key})]})})]},l.key)})})]})}const i1={umo:"会话",provider:"Provider",user:"用户"},nq={umo:"如 qq:12345 / platform:session_id",provider:"",user:"发送者 ID(QQ / 微信 / 钉钉)"},a1={stop:"硬拦截",fallback:"切备用",warn:"仅警告"};function iq({row:e,index:t,total:r,providers:n,fallbackProviders:i,onChange:a,onMove:o,onDelete:l}){const s=e.target_type,u=e.on_exceeded,f=(e.token_limit||0)>0,c=(e.cost_limit||0)>0,d=f||c;return p.jsxs("div",{className:`override-card ${e.enabled?"":"is-disabled"}`,children:[p.jsxs("div",{className:"override-main",children:[p.jsx("input",{type:"checkbox",className:"ov-enable",checked:e.enabled,onChange:h=>a({enabled:h.target.checked}),title:"启用"}),p.jsx("span",{className:"override-idx",children:t+1}),p.jsx("span",{className:"ov-sep",children:"当"}),p.jsx("select",{className:"override-target",value:s,onChange:h=>a({target_type:h.target.value,target_value:""}),children:Object.keys(i1).map(h=>p.jsx("option",{value:h,children:i1[h]},h))}),s==="provider"?p.jsxs("select",{className:"override-value",value:e.target_value,onChange:h=>a({target_value:h.target.value}),children:[p.jsx("option",{value:"",children:"选择 Provider"}),(n||[]).map(h=>p.jsxs("option",{value:h.id,children:[h.id,h.model?` (${h.model})`:""]},h.id))]}):p.jsx("input",{className:"override-value",value:e.target_value,onChange:h=>a({target_value:h.target.value}),placeholder:nq[s]}),p.jsx("span",{className:"ov-sep ov-sep-dot",children:"·"}),p.jsxs("label",{className:"ov-limit",children:[p.jsx("span",{className:"muted small",children:"Token≤"}),p.jsx("input",{type:"number",min:"0",className:"budget-input",value:e.token_limit||0,onChange:h=>a({token_limit:Math.max(0,+h.target.value||0)}),placeholder:"0"})]}),p.jsxs("label",{className:"ov-limit",children:[p.jsxs("span",{className:"muted small",children:[Or(e.cost_currency||du()),"≤"]}),p.jsx("input",{type:"number",min:"0",step:"0.01",className:"budget-input",value:e.cost_limit||0,onChange:h=>a({cost_limit:Math.max(0,+h.target.value||0)}),placeholder:"0"}),p.jsxs("select",{className:"budget-input",value:e.cost_currency||"",onChange:h=>a({cost_currency:h.target.value}),title:"花费货币(留空=主货币)",children:[p.jsx("option",{value:"",children:"主货币"}),Ic.map(h=>p.jsxs("option",{value:h,children:[h," (",Or(h),")"]},h))]})]}),p.jsx("span",{className:"ov-sep",children:"→"}),p.jsx("select",{className:"override-on",value:u,onChange:h=>a({on_exceeded:h.target.value}),title:"超限处理",children:Object.keys(a1).map(h=>p.jsx("option",{value:h,children:a1[h]},h))}),p.jsxs("div",{className:"override-ops",children:[p.jsx("button",{type:"button",className:"move-btn",disabled:t===0,onClick:()=>o("up"),title:"上移",children:"↑"}),p.jsx("button",{type:"button",className:"move-btn",disabled:t===r-1,onClick:()=>o("down"),title:"下移",children:"↓"}),p.jsx("button",{type:"button",className:"move-btn del",onClick:l,title:"删除",children:"✕"})]})]}),d&&p.jsxs("div",{className:"override-status",children:[f&&p.jsx(o1,{label:"token",used:e.current?.token?.used||0,limit:e.token_limit||0,ratio:e.current?.token?.ratio||0,exceeded:!!e.current?.token?.exceeded,fmt:pe}),c&&p.jsx(o1,{label:"cost",used:e.current?.cost?.used||0,limit:e.current?.cost?.limit||0,ratio:e.current?.cost?.ratio||0,exceeded:!!e.current?.cost?.exceeded,fmt:hn,prefix:Or(du())})]}),u==="stop"&&p.jsxs("div",{className:"override-extra",children:[p.jsx("span",{className:"muted small",children:"拦截文案"}),p.jsx("input",{className:"budget-input",value:e.stop_message||"",placeholder:"留空 = 默认文案(含维度 / used / limit)",onChange:h=>a({stop_message:h.target.value})})]}),u==="fallback"&&p.jsxs("div",{className:"override-extra",children:[p.jsx("span",{className:"muted small",children:"备用(按序)"}),p.jsx(aq,{selected:e.fallback_provider_ids,candidates:i,onChange:h=>a({fallback_provider_ids:h})}),p.jsxs("label",{className:"ov-limit",style:{marginLeft:"auto"},children:[p.jsx("span",{className:"muted small",children:"history 截断"}),p.jsx("input",{type:"number",min:"0",className:"budget-input",value:e.fallback_token_limit||0,onChange:h=>a({fallback_token_limit:Math.max(0,+h.target.value||0)}),style:{width:80}}),p.jsx("span",{className:"muted small",children:"0=不限"})]})]})]})}function o1({label:e,used:t,limit:r,ratio:n,exceeded:i,fmt:a,prefix:o=""}){const l=Math.min(100,Math.max(0,n||0)),s=i?"bad":l>=80?"warn":"";return p.jsxs("span",{className:`ov-stat ${s}`,children:[p.jsx("span",{className:"muted small",children:e}),p.jsx("i",{className:"ov-bar",style:{backgroundSize:`${l}% 100%`}}),p.jsxs("span",{className:"ov-pct",children:[l,"%"]}),p.jsxs("span",{className:"muted small",children:[o,a(t)," / ",o,a(r)]})]})}function aq({selected:e,candidates:t,onChange:r}){const n=t.length===0;return p.jsxs("div",{className:"fb-picker",children:[e.map((i,a)=>p.jsxs("span",{className:"provider-tag",children:[i,p.jsx("button",{type:"button",className:"tag-del",onClick:()=>{const o=e.slice();o.splice(a,1),r(o)},children:"✕"})]},`${i}-${a}`)),n?p.jsx("span",{className:"muted small",children:"请先在下方「备用 Provider 库」添加(否则将降级为硬拦截)"}):p.jsxs("select",{className:"fb-add",value:"",onChange:i=>{const a=i.target.value;a&&!e.includes(a)&&r([...e,a]),i.target.value=""},children:[p.jsx("option",{value:"",children:"+ 从备用库添加"}),t.map(i=>p.jsxs("option",{value:i.id,children:[i.id,i.note?` · ${i.note}`:""]},i.id))]})]})}function oq({overrides:e,providers:t,fallbackProviders:r,onChange:n,onMove:i,onDelete:a,onAdd:o}){return p.jsxs("div",{className:"overrides-panel",children:[p.jsxs("div",{className:"muted small",style:{marginBottom:8},children:["按序匹配,第一条命中即生效:token / cost 任一超限 → 按本规则的",p.jsx("b",{children:" 超限处理 "}),"执行(不走全局);未命中或未超限 → 回落全局。"]}),e.length===0?p.jsx("div",{className:"muted small",style:{textAlign:"center",padding:"20px 0"},children:"暂无规则(仅按全局预算生效)"}):p.jsx("div",{className:"override-list",children:e.map((l,s)=>p.jsx(iq,{row:l,index:s,total:e.length,providers:t,fallbackProviders:r,onChange:u=>n(s,u),onMove:u=>i(s,u),onDelete:()=>a(s)},l.id||`ov-${s}`))}),p.jsx("div",{style:{marginTop:8},children:p.jsx("button",{type:"button",className:"btn",onClick:o,children:"+ 添加规则"})})]})}function lq({providers:e,realProviders:t,onChange:r,onDelete:n,onAdd:i}){const a="fb-provider-options";return p.jsxs("div",{className:"fallback-providers",children:[p.jsxs("div",{className:"muted small",style:{marginBottom:8},children:["备用 Provider 库:被「局部阈值」规则的 on_exceeded=fallback 引用。 可填与下方「实际 Provider」不同的标识(人工兜底 ID);实际可调用性以",p.jsx("code",{children:" context.get_provider_by_id "})," 为准。"]}),p.jsx("datalist",{id:a,children:(t||[]).map(o=>p.jsx("option",{value:o.id,children:o.model?`${o.id} (${o.model})`:o.id},o.id))}),p.jsxs("table",{children:[p.jsx("thead",{children:p.jsxs("tr",{children:[p.jsx("th",{style:{width:30}}),p.jsx("th",{children:"Provider ID"}),p.jsx("th",{children:"备注"}),p.jsx("th",{style:{width:40}})]})}),p.jsx("tbody",{children:e.length===0?p.jsx("tr",{children:p.jsx("td",{colSpan:4,className:"muted small",style:{textAlign:"center"},children:"暂无备用 Provider(点击下方「添加」新增)"})}):e.map((o,l)=>p.jsxs("tr",{children:[p.jsx("td",{children:p.jsx("input",{type:"checkbox",checked:o.enabled,onChange:s=>r(l,{enabled:s.target.checked})})}),p.jsx("td",{children:p.jsx("input",{className:"budget-input mono",list:a,value:o.id,onChange:s=>r(l,{id:s.target.value}),placeholder:"从下拉选择或手动输入",style:{width:"100%"}})}),p.jsx("td",{children:p.jsx("input",{className:"budget-input",value:o.note||"",onChange:s=>r(l,{note:s.target.value}),style:{width:"100%"},placeholder:"(可选)"})}),p.jsx("td",{children:p.jsx("button",{type:"button",className:"btn",onClick:()=>n(l),children:"✕"})})]},`${o.id}-${l}`))})]}),p.jsx("div",{style:{marginTop:8},children:p.jsx("button",{type:"button",className:"btn",onClick:()=>i(),children:"+ 添加备用 Provider"})})]})}function Qm({status:e,error:t}){if(e==="idle")return null;const r=e==="saving"?"正在保存…":e==="saved"?"✅ 已保存":`❌ 保存失败:${t||"未知错误"}`;return p.jsx("div",{className:`save-toast save-toast-${e}`,role:"status","aria-live":"polite",children:r})}const sq={umo:"",provider:"",user:""};function uq(e="umo"){return{id:`ov-${Date.now()}-${Math.random().toString(36).slice(2,6)}`,enabled:!0,target_type:e,target_value:sq[e],token_limit:0,cost_limit:0,on_exceeded:"stop",stop_message:"",fallback_provider_ids:[],fallback_token_limit:0,current:{token:{used:0,ratio:0,exceeded:!1},cost:{used:0,limit:0,ratio:0,exceeded:!1}}}}function cq({refreshNonce:e}){const t=ut(()=>fe.getBudgets(),[e]),r=ut(()=>fe.getProviders(),[e]),n=t.data,[i,a]=A.useState({}),[o,l]=A.useState({}),[s,u]=A.useState({}),[f,c]=A.useState([]),[d,h]=A.useState([]),[v,g]=A.useState("stop"),[w,y]=A.useState(!1);A.useEffect(()=>{n&&(a({...n.limits||{}}),l({...n.limits_cost||{}}),u({...n.limits_cost_currency||{}}),c((n.overrides||[]).map($=>({...$}))),h((n.fallback_providers||[]).map($=>({...$}))),g(n.global_default_on_exceeded||"stop"),y(!0))},[n]);const m=r.data?.providers||[],x=A.useMemo(()=>d.filter($=>$.enabled).map($=>$.id),[d]),S=n?.dimensions||{},b=($,L)=>a(F=>({...F,[$]:Math.max(0,parseInt(L,10)||0)})),_=($,L)=>l(F=>({...F,[$]:Math.max(0,+L||0)})),O=($,L)=>u(F=>({...F,[$]:L})),j=($,L)=>c(F=>F.map((M,I)=>I===$?{...M,...L}:M)),P=($,L)=>c(F=>{const M=F.slice();return L==="up"&&$>0?[M[$-1],M[$]]=[M[$],M[$-1]]:L==="down"&&$c(L=>L.filter((F,M)=>M!==$)),N=()=>c($=>[...$,uq("umo")]),k=($,L)=>h(F=>F.map((M,I)=>I===$?{...M,...L}:M)),R=$=>h(L=>L.filter((F,M)=>M!==$)),C=($="")=>h(L=>[...L,{id:$||"",enabled:!0,note:""}]),D=A.useMemo(()=>({budgets:i,budgets_cost:o,budgets_cost_currency:s,budget_overrides:f.filter($=>$.target_value&&$.target_value.trim()).map(({current:$,id:L,...F})=>F),fallback_providers:d.filter($=>$.id&&$.id.trim()),default_on_exceeded:v}),[i,o,s,f,d,v]),{status:U,error:W}=Ym(D,async $=>{await fe.postSaveConfig($)},{enabled:w});return t.loading&&!n?p.jsx(Xr,{}):t.error?p.jsx(Tn,{message:`加载预算失败:${t.error}`}):p.jsxs("div",{children:[p.jsxs(Ne,{children:[p.jsx("h2",{children:"预算总览(5 维全局默认)"}),p.jsxs("div",{className:"muted small",style:{marginBottom:8},children:["Token 与花费两列均可填写,修改后自动保存。 ",p.jsx("code",{children:" per_*_daily "}),"类维度显示的是本周期消耗最多的代表会话 / 模型,并非该维度的全量聚合(运行时按当前请求实时拦截)。"]}),p.jsx(rq,{limits:i,limitsCost:o,budgetsCostCurrency:s,dimensions:S,onChangeLimit:b,onChangeLimitCost:_,onChangeCostCurrency:O})]}),p.jsxs(Ne,{children:[p.jsx("h2",{children:"局部阈值(优先级高于全局)"}),p.jsx(oq,{overrides:f,providers:m,fallbackProviders:x.map($=>({id:$,enabled:!0})),onChange:j,onMove:P,onDelete:T,onAdd:N})]}),p.jsxs(Ne,{children:[p.jsx("h2",{children:"备用 Provider 库"}),p.jsx(lq,{providers:d,realProviders:m,onChange:k,onDelete:R,onAdd:C})]}),p.jsxs(Ne,{children:[p.jsx("div",{className:"budget-head",children:p.jsx("h2",{children:"全局默认超限处理"})}),p.jsx("div",{className:"muted small",style:{marginBottom:6},children:"当 override 未命中且全局 5 维超限时,按此选项处理。"}),p.jsxs("select",{value:v,onChange:$=>g($.target.value),children:[p.jsx("option",{value:"stop",children:"硬拦截"}),p.jsx("option",{value:"fallback",children:"切换备用 Provider(按备用库顺序)"}),p.jsx("option",{value:"warn",children:"仅警告(不中断)"})]})]}),p.jsx(Qm,{status:U,error:W})]})}function iP({segments:e}){const t=e.reduce((r,n)=>r+(n.value||0),0);return t<=0?p.jsx("div",{className:"empty",children:"暂无组件数据"}):p.jsxs(p.Fragment,{children:[p.jsx("div",{className:"stacked-bar",children:e.map(r=>{const n=Math.round(r.value*100/t);return n<=0?null:p.jsx("div",{className:"stacked-seg",style:{width:`${n}%`,background:r.color},title:r.tooltip?`${r.label} ${n}% ${r.tooltip}`:`${r.label} ${n}%`,children:n>=8?`${n}%`:""},r.label)})}),p.jsx("div",{className:"legend",children:e.map(r=>{const n=t>0?Math.round(r.value*100/t):0,i=p.jsxs(p.Fragment,{children:[p.jsx("span",{className:"legend-dot",style:{background:r.color}}),r.label," ",pe(r.value)," (",n,"%)"]});return r.tooltip?p.jsxs("span",{className:"legend-item legend-item-tip",children:[i,p.jsx("span",{className:"legend-tip",children:r.tooltip})]},r.label):p.jsx("span",{className:"legend-item",children:i},r.label)})})]})}const aP=3,fq={context_reset:{title:"上下文重置",tip:"检查上下文是否被截断 / 会话被重置 / 历史被清空"},system_prompt_change:{title:"system 变更",tip:"稳定 system prompt,避免每轮改动前缀导致缓存失效"},tools_change:{title:"工具定义变更",tip:"保持 func_tool 集合稳定,避免增删工具破坏缓存键"},order_drift:{title:"顺序漂移",tip:"避免重排或改写历史消息,保持追加式增长"}};function Rs(e){return fq[e||""]||{title:e||"?",tip:""}}function Io(e){return e!=null?String(e):"-"}function dq(e,t,r){return e==="context_reset"?`历史 ${Io(t?.history_len)} → ${Io(r?.history_len)}`:e==="system_prompt_change"?`system ${t?.system_hash||"-"} → ${r?.system_hash||"-"}`:e==="tools_change"?`tools ${t?.tools_hash||"-"} → ${r?.tools_hash||"-"}`:e==="order_drift"?`首个分歧 #${Io(r?.first_diverge_at)}`:""}function pq(e,t){const r=[];let n=[];const i=()=>{n.length!==0&&(r.push({kind:"lines",lines:n}),n=[])};for(const a of e)a.op===" "?n.push(a):(i(),r.push({kind:"lines",lines:[a]}));return i(),r.map(a=>(a.lines.every(o=>o.op===" ")&&a.lines.length>t,a))}function hq(e,t){const n=[];return e.forEach(i=>{const a=i.lines.every(o=>o.op===" ")&&i.lines.length>aP;if(t&&a&&i.lines.length>3*2){const o=i.lines.slice(0,3),l=i.lines.slice(i.lines.length-3),s=i.lines.length-3*2;n.push(...o),n.push({kind:"placeholder",count:s,segments:[i]}),n.push(...l)}else n.push(...i.lines)}),n}function vq(e){const t=e.before||{},r=e.after||{},n=(u,f,c)=>({label:u,before:Io(f),after:Io(c),changed:String(f)!==String(c)}),i=[n("历史长度",t.history_len,r.history_len),n("system hash",t.system_hash,r.system_hash),n("tools hash",t.tools_hash,r.tools_hash)],a=e.type==="order_drift"&&r.first_diverge_at!=null?r.first_diverge_at:void 0;if(e.type==="tools_change"){const u=t.tools_text||"",f=r.tools_text||"";if(u||f)return{rows:i,firstDiv:a,tip:Rs(e.type).tip,detail:e.detail||"",toolsCompare:{before:u,after:f}}}const o=(r.system_diff||[]).filter(u=>u&&u.op);if(o.length===0)return{rows:i,firstDiv:a,tip:Rs(e.type).tip,detail:e.detail||""};const l="system prompt 变更",s=pq(o,aP);return{rows:i,firstDiv:a,tip:Rs(e.type).tip,detail:e.detail||"",diff:{label:l,segments:s,initiallyCollapsed:!0}}}function mq({ev:e}){const[t,r]=A.useState(!1),[n,i]=A.useState(!0),a=(e.severity||"low").toLowerCase(),o=Rs(e.type),l=dq(e.type||"",e.before,e.after),s=vq(e),u=!!s.diff&&n,f=s.diff?hq(s.diff.segments,u):[];return p.jsxs(p.Fragment,{children:[p.jsxs("tr",{className:"cache-event-row",onClick:()=>r(c=>!c),children:[p.jsxs("td",{children:[p.jsx("span",{className:"cache-evt-title",children:o.title}),p.jsx("div",{className:"muted small",children:e.type||""})]}),p.jsx("td",{children:p.jsx("span",{className:`tag sev-${a}`,children:e.severity||"-"})}),p.jsx("td",{className:"mono",title:e.umo||"",children:e.umo||"-"}),p.jsx("td",{children:Bv(e.created_at)}),p.jsx("td",{className:"mono small",children:l}),p.jsx("td",{className:"toggle",children:t?"▼":"▶"})]}),t&&p.jsx("tr",{className:"cache-event-detail",children:p.jsxs("td",{colSpan:6,children:[p.jsxs("div",{className:"diff-grid",children:[s.rows.map((c,d)=>p.jsxs("div",{className:`diff-row ${c.changed?"diff-changed":""}`,children:[p.jsx("span",{className:"diff-label",children:c.label}),p.jsx("span",{className:"diff-before mono",children:c.before}),p.jsx("span",{className:"diff-arrow",children:"→"}),p.jsx("span",{className:"diff-after mono",children:c.after})]},d)),s.firstDiv!=null&&p.jsxs("div",{className:"diff-row diff-changed",children:[p.jsx("span",{className:"diff-label",children:"首个分歧"}),p.jsx("span",{className:"diff-before mono"}),p.jsx("span",{className:"diff-arrow"}),p.jsxs("span",{className:"diff-after mono",children:["#",s.firstDiv]})]})]}),s.toolsCompare&&p.jsxs("div",{className:"tools-compare",children:[p.jsxs("div",{className:"tools-compare-col",children:[p.jsx("div",{className:"tools-compare-label",children:"变更前"}),p.jsx("pre",{className:"tools-compare-body",children:s.toolsCompare.before||"(无)"})]}),p.jsx("div",{className:"tools-compare-arrow",children:"→"}),p.jsxs("div",{className:"tools-compare-col",children:[p.jsx("div",{className:"tools-compare-label",children:"变更后"}),p.jsx("pre",{className:"tools-compare-body",children:s.toolsCompare.after||"(无)"})]})]}),s.diff&&f.length>0&&p.jsxs("div",{className:"gitdiff",children:[p.jsx("div",{className:"gitdiff-label",children:s.diff.label}),p.jsxs("pre",{className:"gitdiff-body",children:[f.map((c,d)=>{if("kind"in c&&c.kind==="placeholder")return p.jsxs("div",{className:"diff-line diff-collapsed",onClick:g=>{g.stopPropagation(),i(!1)},children:[p.jsx("span",{className:"dl-sign",children:"⋯"}),p.jsxs("span",{className:"dl-text",children:["隐藏 ",c.count," 行未变更上下文,点击展开"]})]},d);const h=c,v=h.op==="+"?"add":h.op==="-"?"del":"ctx";return p.jsxs("div",{className:`diff-line dl-${v}`,children:[p.jsx("span",{className:"dl-sign",children:h.op}),p.jsx("span",{className:"dl-text",children:h.text})]},d)}),u&&p.jsxs("div",{className:"diff-line diff-collapsed diff-expand-all",onClick:c=>{c.stopPropagation(),i(!1)},children:[p.jsx("span",{className:"dl-sign",children:"▾"}),p.jsx("span",{className:"dl-text",children:"展开全部"})]})]})]}),s.tip&&p.jsxs("div",{className:"diff-tip",children:[p.jsx("strong",{children:"处置建议:"}),s.tip]}),s.detail&&p.jsx("div",{className:"muted small",children:s.detail})]})})]})}function yq({window:e,refreshNonce:t}){const r=ut(()=>fe.getCache(e),[e,t]);if(r.loading&&!r.data)return p.jsx(Xr,{});if(r.error)return p.jsx(Tn,{message:`加载缓存诊断失败:${r.error}`});const n=r.data,i=n?.events||[],a=n?.total_input_cached||0,o=n?.total_input_other||0,l=n?.total_output||0,s=a+o+l,u=[{label:"缓存命中",value:a,color:"var(--ok)"},{label:"缓存未命中",value:o,color:"var(--warn)"},{label:"输出",value:l,color:"var(--accent)"}];return p.jsxs("div",{children:[p.jsx(zv,{items:[{label:"平均缓存命中率",value:`${n?.cache_hit_rate||0}%`,sub:`${n?.samples||0} 样本`},{label:"破坏事件",value:pe(i.length)},{label:"非缓存输入 token",value:pe(o),sub:"可经提升命中率优化"}]}),p.jsxs(Ne,{children:[p.jsx("h2",{children:"Token 占比"}),s>0?p.jsxs(p.Fragment,{children:[p.jsx(iP,{segments:u}),n?.cache_note&&p.jsx("div",{className:"muted small",style:{marginTop:8},children:n.cache_note})]}):p.jsx(Vt,{text:"暂无 token 数据"})]}),p.jsxs(Ne,{children:[p.jsx("h2",{children:"缓存破坏事件(最近)"}),i.length===0?p.jsx(Vt,{text:"未检测到缓存破坏事件"}):p.jsxs("table",{className:"cache-events",children:[p.jsx("thead",{children:p.jsxs("tr",{children:[p.jsx("th",{children:"类型"}),p.jsx("th",{children:"严重度"}),p.jsx("th",{children:"会话"}),p.jsx("th",{children:"时间"}),p.jsx("th",{children:"前后变化"}),p.jsx("th",{})]})}),p.jsx("tbody",{children:i.map((f,c)=>p.jsx(mq,{ev:f},c))})]})]})]})}function gq({window:e,refreshNonce:t}){const r=ut(()=>fe.getAttribution(e),[e,t]);if(r.loading&&!r.data)return p.jsx(Xr,{});if(r.error)return p.jsx(Tn,{message:`加载上下文失败:${r.error}`});const n=r.data,i=n?.avg_components||{},a=[{label:"system",value:i.system||0,color:"var(--accent)",tooltip:"系统提示词,定义 LLM 的角色与行为规则。来源:AstrBot 全局配置、插件注入的系统指令。"},{label:"tools",value:i.tools||0,color:"#8ab4ff",tooltip:"工具/函数定义(function calling),声明 LLM 可调用的工具。来源:已注册的函数工具、插件提供的工具。"},{label:"history",value:i.history||0,color:"var(--warn)",tooltip:"对话历史,即之前的多轮消息上下文。来源:会话记录中的历史消息,随轮次累积增长。"},{label:"user",value:i.user||0,color:"var(--ok)",tooltip:"当前轮用户输入,包括文本与图片/音频等媒体。来源:用户的原始发言。"},{label:"extra",value:i.extra||0,color:"#c084fc",tooltip:"插件注入的额外用户内容块。来源:其他插件通过 extra_user_content_parts 追加的指令、提醒、上下文等。"}],o=a.reduce((u,f)=>u+f.value,0),l=o>0?Math.round((i.history||0)*100/o):0,s=n?.recent||[];return p.jsxs("div",{children:[p.jsx(zv,{items:[{label:"system 平均",value:pe(i.system)},{label:"tools 平均",value:pe(i.tools)},{label:"history 平均",value:pe(i.history)},{label:"user 平均",value:pe(i.user)},{label:"extra 平均",value:pe(i.extra)}]}),p.jsxs(Ne,{children:[p.jsx("h2",{children:"组件占比(平均)"}),o>0?p.jsxs(p.Fragment,{children:[p.jsx(iP,{segments:a}),l>=40&&p.jsxs("div",{className:"alert-body",style:{marginTop:10},children:["history 占注入的 ",p.jsxs("strong",{children:[l,"%"]}),",是可优化的主要部分——精简历史可显著降低每轮输入 token。"]}),n?.estimation_note&&p.jsx("div",{className:"muted small",style:{marginTop:8},children:n.estimation_note})]}):p.jsx(Vt,{text:"暂无组件数据"})]}),p.jsxs(Ne,{children:[p.jsx("h2",{children:"最近请求上下文"}),s.length===0?p.jsx(Vt,{text:"暂无上下文数据"}):p.jsxs("table",{children:[p.jsx("thead",{children:p.jsxs("tr",{children:[p.jsx("th",{children:"时间"}),p.jsx("th",{children:"会话"}),p.jsx("th",{children:"注入 token"}),p.jsx("th",{children:"system"}),p.jsx("th",{children:"tools"}),p.jsx("th",{children:"history"}),p.jsx("th",{children:"user"}),p.jsx("th",{children:"extra"})]})}),p.jsx("tbody",{children:s.map((u,f)=>{const c=u.attribution||{};return p.jsxs("tr",{children:[p.jsx("td",{children:Bv(u.created_at)}),p.jsx("td",{className:"mono",title:u.umo||"",children:u.umo||"-"}),p.jsx("td",{children:u.injection_total==null?"-":pe(u.injection_total)}),p.jsx("td",{children:pe(c.system)}),p.jsx("td",{children:pe(c.tools)}),p.jsx("td",{children:pe(c.history)}),p.jsx("td",{children:pe(c.user)}),p.jsx("td",{children:pe(c.extra)})]},f)})})]})]})]})}function xo({children:e,onClick:t,variant:r="default",disabled:n,title:i}){const a=r==="primary"?"btn primary":r==="danger"?"btn btn-danger":"btn";return p.jsx("button",{className:a,onClick:t,disabled:n,title:i,children:e})}function l1(e){const t=Number(e);return Number.isFinite(t)&&t>=.01&&t<=100?t:1}function bq({clusters:e,selectedId:t,onSelect:r,multipliers:n,onMultiplierChange:i,renderProvider:a}){const[o,l]=A.useState(null),s=e.find(c=>c.id===t)??e[0];if(!s)return null;const u=n[s.id]??"1",f=l1(u);return p.jsxs("div",{className:"pricing-catalog",children:[p.jsxs("aside",{className:"pricing-cluster-sidebar","aria-label":"AstrBot 供应商目录",children:[p.jsx("div",{className:"pricing-cluster-sidebar-title",children:"ASTRBOT 供应商"}),p.jsx("div",{className:"pricing-cluster-directory",role:"tablist","aria-orientation":"vertical",children:e.map(c=>{const d=l1(n[c.id]??"1"),h=c.id===s.id;return p.jsxs("button",{type:"button",role:"tab","aria-selected":h,className:`pricing-cluster-item ${h?"is-active":""}`,onClick:()=>r(c.id),children:[p.jsxs("span",{className:"pricing-cluster-item-main",children:[p.jsx("span",{className:"pricing-cluster-name",children:c.name}),p.jsx("span",{className:"pricing-cluster-count",children:c.provider_ids.length})]}),d!==1&&p.jsxs("span",{className:"pricing-cluster-factor",children:[d,"×"]})]},c.id)})})]}),p.jsxs("section",{className:"pricing-cluster-detail",role:"tabpanel",children:[p.jsxs("div",{className:"pricing-cluster-detail-head",children:[p.jsxs("div",{children:[p.jsxs("div",{className:"pricing-cluster-detail-title-row",children:[p.jsx("h3",{children:s.name}),p.jsxs("span",{className:"pricing-cluster-model-count",children:[s.provider_ids.length," 个现有模型配置"]}),p.jsxs("span",{className:`pricing-cluster-current-factor ${f!==1?"is-custom":""}`,children:["当前 ",f,"×"]})]}),p.jsx("div",{className:"muted small",children:"卡片显示当前自定义价或内置匹配价;展开即可直接修改。"})]}),p.jsx("button",{type:"button",className:"btn pricing-multiplier-toggle","aria-expanded":o===s.id,onClick:()=>l(c=>c===s.id?null:s.id),children:o===s.id?"收起倍率":"设置聚类倍率"})]}),o===s.id&&p.jsxs("div",{className:"pricing-multiplier-editor",children:[p.jsxs("div",{className:"pricing-multiplier-copy",children:[p.jsxs("span",{className:"pricing-multiplier-label",children:[s.name," 供应商倍率"]}),p.jsx("span",{className:"muted small",children:"对该 AstrBot Provider Source 下的现有模型统一相乘;卡片中仍填写基准价。"})]}),p.jsxs("label",{className:"pricing-multiplier-input-wrap",children:[p.jsx("input",{type:"number",min:"0.01",max:"100",step:"0.05",className:"budget-input pricing-multiplier-input",value:u,onChange:c=>i(s.id,c.target.value),onBlur:()=>{const c=Number(u);(!Number.isFinite(c)||c<.01||c>100)&&i(s.id,"1")},"aria-label":`${s.name} 供应商倍率`}),p.jsx("span",{children:"×"})]}),f!==1&&p.jsx("button",{type:"button",className:"pricing-multiplier-reset",onClick:()=>i(s.id,"1"),children:"恢复 1×"})]}),p.jsx("div",{className:"pricing-rule-scroll pricing-provider-rule-scroll",children:s.provider_ids.map(c=>a(c,f))})]})]})}const oP=[{key:"input",label:"输入"},{key:"input_cached",label:"缓存命中"},{key:"output",label:"输出"},{key:"cache_creation",label:"缓存写入"}];function jd(e){const t={mode:e?.mode??"per_token",input:"",input_cached:"",output:"",cache_creation:"",price:"",currency:e?.currency??""};return e&&(e.mode==="per_token"?(t.input=e.input!=null?String(e.input):"",t.input_cached=e.input_cached!=null?String(e.input_cached):"",t.output=e.output!=null?String(e.output):"",t.cache_creation=e.cache_creation!=null&&e.cache_creation!==void 0?String(e.cache_creation):""):t.price=e.price!=null?String(e.price):""),t}function Bs(e){return e.mode==="per_token"?oP.every(t=>e[t.key].trim()===""):e.price.trim()===""}function xq(e){if(Bs(e))return null;if(e.mode==="per_token"){const i={mode:"per_token",input:0,input_cached:0,output:0};let a=!1;const o=(l,s)=>{if(s.trim()==="")return;const u=parseFloat(s);if(Number.isNaN(u)||u<0)throw new Error("非法数值");l==="cache_creation"?i.cache_creation=u:i[l]=u,a=!0};return o("input",e.input),o("input_cached",e.input_cached),o("output",e.output),o("cache_creation",e.cache_creation),a?(e.currency&&e.currency!=="USD"&&(i.currency=e.currency),i):null}const t=e.price.trim(),r=parseFloat(t);if(Number.isNaN(r)||r<0)throw new Error("单价非法数值");const n={mode:e.mode,price:r};return e.currency&&e.currency!=="USD"&&(n.currency=e.currency),n}const wq=[{value:"per_token",label:"按 Token"},{value:"per_turn",label:"按调用轮次"},{value:"per_request",label:"按请求次数"}];function Sq(e,t,r){const n=`${r} (${t})`;return e==="per_token"?`${n} / 百万 token。留空 = 用内置默认价。输入即覆盖默认。`:e==="per_turn"?`${n} / 次。每次 LLM 调用(含 function-calling 每一步)固定费用。`:`${n} / 次。每次用户请求固定费用(一次请求含多步调用只计一次)。`}function _q(e,t,r){if(r){if(e.mode==="per_token"){const n=[];return e.input.trim()&&n.push(`输入 ${e.input}`),e.output.trim()&&n.push(`输出 ${e.output}`),n.join(" / ")||"空"}return`${e.price} ${e.currency||"USD"} / ${e.mode==="per_turn"?"轮":"次"}`}if(t){const n=t.entry,i=[];return n.input!=null&&i.push(`输入 ${n.input}`),n.output!=null&&i.push(`输出 ${n.output}`),i.join(" / ")||"默认"}return"未定价"}function Oq({providerId:e,displayId:t,type:r,candidates:n,draft:i,matchedDefault:a,hasUserOverride:o,isDeletedResidue:l,hasUsage:s,highlightSignal:u,onChange:f,onClear:c,onDeleteData:d}){const h=A.useRef(null),[v,g]=A.useState(!a),[w,y]=A.useState(!1),[m,x]=A.useState(!1),[S,b]=A.useState(""),_=A.useRef(null);A.useEffect(()=>()=>{_.current&&clearTimeout(_.current)},[]),A.useEffect(()=>{u&&u>0&&h.current&&(h.current.scrollIntoView({behavior:"smooth",block:"center"}),h.current.classList.remove("pricing-pulse"),h.current.offsetWidth,h.current.classList.add("pricing-pulse"))},[u]);const O=i.currency||"USD",j=Or(O),P=C=>{if(C==="price"||!a?.entry)return"";const D=a.entry[C];return D!=null?String(D):""},T=C=>{f(C==="per_token"?{mode:C,price:""}:{mode:C,input:"",input_cached:"",output:"",cache_creation:""})},N=!o&&!a,k=async()=>{if(!(!d||m)){if(!w){y(!0),b(""),_.current&&clearTimeout(_.current),_.current=setTimeout(()=>y(!1),4e3);return}_.current&&clearTimeout(_.current),y(!1),x(!0),b("");try{await d()}catch(C){b(C instanceof Error?C.message:String(C)),x(!1)}}},R=["pricing-card",l?"is-deleted-residue":"",v?"":"is-collapsed",N&&s?"is-unpriced-alert":"",N&&!s?"is-unpriced-warn":""].filter(Boolean).join(" ");return p.jsxs("div",{className:R,ref:h,children:[p.jsxs("div",{className:"pricing-card-head",onClick:()=>!v&&g(!0),style:v?void 0:{cursor:"pointer"},children:[p.jsxs("div",{className:"pricing-id-wrap",children:[p.jsx("span",{className:"mono pricing-id",children:t||e}),t&&t!==e&&p.jsx("span",{className:"mono pricing-provider-source-id",title:"Provider ID",children:e}),l&&p.jsx("span",{className:"pricing-tag-residue",title:"该 Provider 已从当前配置删除,此处仅保留历史用量或旧定价",children:"已删除供应商残留"}),r&&p.jsx("span",{className:"muted small",children:r}),o?p.jsx("span",{className:"pricing-badge pricing-badge--blue",children:"自定义"}):a?p.jsx("span",{className:"pricing-badge pricing-badge--gray",children:"内置匹配"}):p.jsx("span",{className:"pricing-badge pricing-badge--red",children:"未定价"})]}),p.jsxs("div",{className:"pricing-head-right",style:{display:"flex",alignItems:"center",gap:8},children:[l&&d&&p.jsx("button",{type:"button",className:`pricing-delete-residue ${w?"is-armed":""}`,disabled:m,onClick:C=>{C.stopPropagation(),k()},title:"永久删除该 Provider 的历史用量、补充记录和旧定价",children:m?"删除中…":w?"⚠ 确认删除":"删除残留数据"}),!v&&p.jsx("span",{className:"pricing-collapsed-summary",children:_q(i,a??null,!!o)}),!v&&p.jsx("button",{type:"button",className:"pricing-expand-btn",title:"展开编辑",children:"▸"}),v&&p.jsxs(p.Fragment,{children:[p.jsxs("label",{className:"pricing-currency-label",title:"该 Provider 计价使用的货币,结算时按汇率换算到主货币",children:[p.jsx("span",{className:"muted small",children:"货币"}),p.jsxs("select",{className:"budget-input pricing-currency-select",value:i.currency,onChange:C=>f({currency:C.target.value}),onClick:C=>C.stopPropagation(),children:[p.jsx("option",{value:"",children:"USD"}),Ic.filter(C=>C!=="USD").map(C=>p.jsx("option",{value:C,children:C},C))]})]}),p.jsx("button",{type:"button",className:"pricing-clear",onClick:C=>{C.stopPropagation(),c()},title:"清除该 Provider 定价(回退默认)",children:"清除"}),p.jsx("button",{type:"button",className:"pricing-collapse-btn",onClick:C=>{C.stopPropagation(),g(!1)},title:"折叠",children:"▾"})]})]})]}),S&&p.jsxs("div",{className:"pricing-delete-error",children:["删除失败:",S]}),v&&p.jsxs(p.Fragment,{children:[n.length>0&&p.jsx("div",{className:"pricing-candidates",children:n.map(C=>p.jsx("span",{className:"provider-tag",children:C},C))}),p.jsx("div",{className:"pricing-mode-row",children:p.jsx(fu,{options:wq,value:i.mode,onChange:T,variant:"weak"})}),p.jsx("div",{className:"muted small pricing-mode-hint",children:Sq(i.mode,O,j)}),p.jsx("div",{className:`pricing-fields pf-${i.mode}`,children:i.mode==="per_token"?oP.map(C=>p.jsxs("label",{className:"pricing-field",children:[p.jsx("span",{className:"muted small",children:C.label}),p.jsx("input",{type:"number",step:"any",min:"0",className:"budget-input",value:i[C.key],placeholder:P(C.key),onChange:D=>f({[C.key]:D.target.value})})]},C.key)):p.jsxs("label",{className:"pricing-field",children:[p.jsxs("span",{className:"muted small",children:[j," / ",i.mode==="per_turn"?"每轮":"每次请求"]}),p.jsx("input",{type:"number",step:"any",min:"0",className:"budget-input",value:i.price,onChange:C=>f({price:C.target.value})})]})})]})]})}function jq({refreshNonce:e}){const t=ut(()=>fe.getPricing(),[e]),r=t.data,[n,i]=A.useState({}),[a,o]=A.useState({}),[l,s]=A.useState(""),[u,f]=A.useState(!1),c=A.useRef(null),[d,h]=A.useState(!1),[v,g]=A.useState(null),[w,y]=A.useState(0),[m,x]=A.useState(""),[S,b]=A.useState(null);A.useEffect(()=>{if(!r)return;const B={},G=r.user_pricing||{};for(const[$t,rn]of Object.entries(G))B[$t]=jd(rn);i(B);const Z={},ce=new Set((r.pricing_clusters||[]).map($t=>$t.id));for(const[$t,rn]of Object.entries(r.pricing_multipliers||{}))ce.size>0&&!ce.has($t)||(Z[$t]=String(rn));o(Z),h(!0),b(null)},[r]);const _=r?.provider_models||[],O=S??r?.unpriced??[],j=A.useMemo(()=>new Set(_.map(B=>B.id)),[_]),P=A.useMemo(()=>{if(r?.deleted_providers)return r.deleted_providers;const B=new Map;for(const G of Object.keys(n))j.has(G)||B.set(G,{provider_id:G,tokens:0,count:0,has_pricing:!0});for(const G of O){const Z=G.provider_id||"";if(!Z||j.has(Z))continue;const ce=B.get(Z)||{provider_id:Z,tokens:0,count:0};ce.tokens+=G.tokens||0,ce.count+=G.count||0,B.set(Z,ce)}return Array.from(B.values()).sort((G,Z)=>Z.tokens-G.tokens||G.provider_id.localeCompare(Z.provider_id))},[r?.deleted_providers,n,O,j]),T=B=>{const G=B.lastIndexOf("/");return G>=0?B.slice(G+1):B},N=A.useMemo(()=>_.map(B=>({id:B.id,displayId:B.model||B.id,type:B.type,candidates:B.candidates,matchedDefault:B.matched_default??null})),[_]),k=A.useMemo(()=>P.map(B=>({id:B.provider_id,displayId:B.provider_id,type:void 0,candidates:B.models||[],matchedDefault:B.matched_default??null,isDeletedResidue:!0})),[P]),R=A.useMemo(()=>{if(r?.pricing_clusters?.length)return r.pricing_clusters.map(G=>({...G,provider_ids:(G.provider_ids||[]).filter(Z=>j.has(Z))})).filter(G=>G.provider_ids.length>0);const B=new Map;for(const G of _){const Z=G.supplier_id||G.id,ce=B.get(Z)||{id:Z,name:G.supplier_name||Z,provider_ids:[]};ce.provider_ids.push(G.id),B.set(Z,ce)}return Array.from(B.values())},[r?.pricing_clusters,_,j]);A.useEffect(()=>{R.some(B=>B.id===m)||x(R[0]?.id??"")},[R,m]);const C=A.useMemo(()=>new Map(N.map(B=>[B.id,B])),[N]),D=A.useMemo(()=>{const B=new Map;for(const G of R)for(const Z of G.provider_ids)B.set(Z,G.id);return B},[R]),U=A.useMemo(()=>{const B=new Map;for(const G of O){const Z=G.provider_id||"(未知)",ce=B.get(Z)||{models:[],totalTokens:0};ce.models.push(G),ce.totalTokens+=G.tokens||0,B.set(Z,ce)}return Array.from(B.entries()).sort((G,Z)=>Z[1].totalTokens-G[1].totalTokens)},[O]),W=A.useMemo(()=>{const B=new Set;for(const G of O){const Z=G.provider_id||"";Z&&B.add(Z)}return B},[O]),$=B=>W.has(B),L=(B,G)=>i(Z=>{const ce=Z[B]??jd(void 0);return{...Z,[B]:{...ce,...G}}}),F=B=>i(G=>{const Z={...G};return delete Z[B],Z}),M=B=>n[B]??jd(void 0),I=(B,G)=>o(Z=>({...Z,[B]:G})),X=()=>{const B={};for(const[G,Z]of Object.entries(n)){if(Bs(Z))continue;const ce=xq(Z);ce&&(B[G]=ce)}return B},J=()=>{const B={};for(const[G,Z]of Object.entries(a)){const ce=Number(Z);if(!Number.isFinite(ce)||ce<.01||ce>100)throw new Error("聚类倍率必须在 0.01–100 之间");Math.abs(ce-1)>1e-12&&(B[G]=ce)}return B},te=A.useMemo(()=>{try{return{pricing:X(),pricing_multipliers:J()}}catch(B){return{pricing:null,pricing_multipliers:null,error:B instanceof Error?B.message:String(B)}}},[n,a]),{status:me,error:Re,flush:ze}=Ym(te,async B=>{if(B.error)throw new Error(B.error);await fe.postSaveConfig({pricing:B.pricing,pricing_multipliers:B.pricing_multipliers});try{const G=await fe.getPricing();b(G.unpriced??[])}catch{}},{enabled:d}),q=async B=>{if(me==="saving")throw new Error("价格配置正在保存,请稍后再试");await ze(),await fe.postDeleteProviderData(B),b(null),t.refetch()};if(t.loading&&!r)return p.jsx(Xr,{});if(t.error)return p.jsx(Tn,{message:`加载定价失败:${t.error}`});const re=async()=>{if(!u){f(!0),s("⚠ 再次点击以确认重置"),c.current&&clearTimeout(c.current),c.current=setTimeout(()=>{f(!1),s("")},4e3);return}c.current&&clearTimeout(c.current),f(!1),s("重置中…");try{await fe.postSaveConfig({pricing:{},pricing_multipliers:{}}),s("✅ 已重置,立即生效"),t.refetch()}catch(B){s(`❌ 重置失败:${B instanceof Error?B.message:String(B)}`)}},ie=B=>{const G=D.get(B);G&&x(G),g(B),y(Z=>Z+1)},H=N.length,je=N.filter(B=>!B.matchedDefault&&Bs(M(B.id))).length,se=B=>p.jsx(Oq,{providerId:B.id,displayId:B.displayId,type:B.type,candidates:B.candidates,draft:M(B.id),matchedDefault:B.matchedDefault,hasUserOverride:!Bs(M(B.id)),isDeletedResidue:B.isDeletedResidue,hasUsage:$(B.id),highlightSignal:v===B.id?w:void 0,onChange:G=>L(B.id,G),onClear:()=>F(B.id),onDeleteData:B.isDeletedResidue?()=>q(B.id):void 0},B.id);return p.jsxs("div",{children:[O.length>0&&p.jsxs(Ne,{className:"alert-panel",children:[p.jsxs("h2",{children:["未定价告警(",U.length," 个 Provider)"]}),p.jsxs("div",{className:"alert-body",children:["以下 Provider 有用量但无定价匹配,成本被计为 ",p.jsx("strong",{children:"$0"}),"。 点击行可快速跳转到对应 Provider 定价卡片。"]}),p.jsx("div",{className:"unpriced-groups",children:U.map(([B,G])=>{const Z=!j.has(B);return p.jsxs("div",{className:`unpriced-group-row ${Z?"is-deleted-residue":""}`,onClick:()=>ie(B),title:Z?"该 Provider 已从当前配置删除,点击查看残留数据":"点击跳转到定价卡片",children:[p.jsx("span",{className:"mono unpriced-pid",children:T(B)||"(未知)"}),Z&&p.jsx("span",{className:"unpriced-residue-tag",children:"已删除供应商残留"}),p.jsxs("span",{className:"unpriced-models",children:[G.models.length," 个模型"]}),p.jsxs("span",{className:"unpriced-tokens",children:[pe(G.totalTokens)," token"]}),p.jsx("span",{className:"unpriced-jump-hint",children:"点击跳转 ▸"})]},B)})})]}),p.jsxs(Ne,{className:"pricing-catalog-panel",children:[p.jsxs("div",{className:"pricing-header",children:[p.jsx("h2",{children:"供应商定价"}),p.jsxs("div",{className:"pricing-header-stats",children:[p.jsxs("span",{className:"muted small",children:[R.length," 个 AstrBot 供应商 · ",H," ","个现有模型配置"]}),je>0&&p.jsxs("span",{className:"pricing-unmatched-count",children:[je," 个未定价"]})]})]}),p.jsxs("div",{className:"muted small pricing-catalog-help",children:["仅显示 AstrBot 当前配置中的 Provider/模型,并按"," ",p.jsx("strong",{children:"provider_source_id"})," ","聚类。同一供应商的模型集中在右侧;内置价格只作为现有模型的默认匹配,展开卡片即可直接覆盖。"]}),R.length>0?p.jsx(bq,{clusters:R,selectedId:m,onSelect:x,multipliers:a,onMultiplierChange:I,renderProvider:B=>{const G=C.get(B);return G?se(G):null}}):p.jsx("div",{className:"muted small",style:{margin:"8px 0"},children:"未获取到当前 AstrBot 的 provider 配置。可在 AstrBot 主配置添加 provider 后重载插件。"}),p.jsxs("div",{className:"row",style:{marginTop:8,gap:10,alignItems:"center"},children:[p.jsx(xo,{onClick:re,title:"清空自定义定价,恢复内置默认匹配",variant:u?"danger":"default",children:u?"⚠ 确认重置":"重置全部"}),p.jsx("span",{className:"muted",children:l})]})]}),k.length>0&&p.jsxs(Ne,{children:[p.jsxs("div",{className:"pricing-header",children:[p.jsx("h2",{children:"已删除供应商残留"}),p.jsxs("span",{className:"muted small",children:[k.length," 个已不在 AstrBot 配置中的 Provider"]})]}),p.jsx("div",{className:"pricing-residue-help",children:"以下内容不属于当前供应商聚类,仅用于清理历史用量、补充记录和旧定价。"}),p.jsx("div",{className:"overrides-list",children:k.map(se)})]}),p.jsx(Qm,{status:me,error:Re})]})}const Pq=[{key:"_master",title:"总开关与全局",desc:"插件的启停、用量「日」窗口的起算时刻,以及主货币等。",fields:[{k:"enabled",label:"启用插件",type:"bool",help:"关闭后插件完全停止:不采集用量、不拦截请求、不推送告警与日报。"},{k:"refresh_time",label:"日窗口起算时刻",type:"str",width:100,help:"本地时区 HH:MM。预算计数与用量报表都按此划分「一天」。例如 09:00 表示 09:00 至次日 09:00 算作一天。"},{k:"currency_symbol",label:"主货币",type:"select",options:Ic,help:"所有费用最终换算并以此货币结算和显示。内置定价以 USD 计价,切换后自动按汇率交叉换算。"}]},{key:"alerts",title:"超预算告警",desc:"超预算时主动推送提醒的策略;关闭后仍会按策略拦截请求,只是不再推送提醒。",fields:[{k:"enabled",label:"启用超预算主动推送",type:"bool",help:"超限时主动发消息提醒。关闭后仍会按策略拦截请求,只是不再推送提醒。"},{k:"cooldown_seconds",label:"告警冷却(秒)",type:"int",width:100,help:"同一告警的最短重复间隔,避免刷屏;0 = 不冷却(每次超限都推)。"}]},{key:"report",title:"每日日报",desc:"每天定时向指定会话推送一次用量与成本汇总。开关、时间与接收方集中在此配置。",fields:[{k:"enable_daily_report",group:"schedule",label:"启用每日用量日报",type:"bool",help:"开启后,每天按下方「日报推送时间」自动推送一次用量汇总到「日报接收方」。"},{k:"daily_report_time",label:"日报推送时间",type:"str",width:100,help:"本地时区 HH:MM,到点自动推送(需开启上方「启用每日用量日报」)。"},{k:"daily_report_to",label:"日报接收方",type:"csv",help:"接收日报的会话 UMO 列表,逗号分隔。在目标会话中向 Bot 发送 /sid 即可获取该会话的 UMO;/sid 是 AstrBot 的内置指令,需在 WebUI「插件管理」中启用「内置指令」插件后才可用。"}]},{key:"cache_diag",title:"缓存诊断",desc:"LLM 通常对重复上下文做缓存,命中后计费的 token 更少。下列检测用于发现缓存意外失效、导致成本上升的情形。",fields:[{k:"detect_context_reset",label:"对话历史被重置",type:"bool",help:"新一轮历史突变或被清空时标记——此前缓存的上下文失效。"},{k:"detect_system_prompt_change",label:"系统提示词变更",type:"bool",help:"system prompt 发生变化时标记——缓存 key 改变而失效。"},{k:"detect_tools_change",label:"工具定义变更",type:"bool",help:"function calling 的工具列表发生变化时标记——缓存失效。"},{k:"detect_order_drift",label:"消息顺序漂移",type:"bool",help:"历史消息顺序被打乱时标记——请求前缀与已缓存内容对不上。"},{k:"cache_hit_rate_alert_enabled",label:"启用命中率告警推送",type:"bool",help:"开启后,当本轮缓存命中率低于下方阈值时,会向当前会话推送一条告警消息。默认关闭,避免刷屏。"},{k:"cache_hit_rate_alert_threshold",label:"命中率告警阈值 (%)",type:"int",width:100,help:"缓存命中率低于此值时告警;0 = 不告警。需先开启上方「启用命中率告警推送」开关。"}]},{key:"attribution",title:"上下文归因",desc:"拆分每次请求的 token 来源占比(系统提示词 / 工具 / 历史对话 / 用户输入),看清上下文膨胀的构成。",fields:[{k:"enabled",label:"启用上下文归因分析",type:"bool",help:"开启后会估算并拆分每次 LLM 请求的 token 来源占比。"},{k:"sample_rate",label:"采样率 (%)",type:"int",width:100,help:"归因分析的采样百分比,100 = 每次都分析;调低可减少开销。"}]},{key:"ai_diag",title:"AI 诊断",desc:"首页「AI 成本诊断」功能使用的 LLM。",fields:[{k:"ai_diag_provider_id",group:"_master",label:"AI 诊断 Provider",type:"dynamic-select",help:"首页「AI 成本诊断」使用的 LLM Provider。留空 = 使用 AstrBot 默认 Provider。"}]},{key:"advanced",title:"高级",desc:"非默认场景下的可选配置,普通用户无需调整。",fields:[{k:"platforms",label:"生效平台",type:"csv",help:"限定插件只处理这些平台的请求(如 aiocqhttp、telegram_official、lark);留空 = 对所有平台生效。"}]}];function s1(e,t,r){const n=t==="_master"?e[r]:e[t]?.[r];return n??""}function Aq({onCurrencyChanged:e}){const t=ut(()=>fe.getConfig(),[]),[r,n]=A.useState({}),[i,a]=A.useState(!1),[o,l]=A.useState(""),[s,u]=A.useState(!1),[f,c]=A.useState(""),[d,h]=A.useState(!1),[v,g]=A.useState(new Set),[w,y]=A.useState(!1),[m,x]=A.useState(""),[S,b]=A.useState(!1),_=A.useRef(null),[O,j]=A.useState([]);A.useEffect(()=>{fe.getAiProvider().then(M=>{M.providers&&j(M.providers)})},[]);const P=[{key:"supplements",label:"补充采集记录",desc:"每请求的 cache 细分、归因注入量、cost_amount 等"},{key:"cache_events",label:"缓存破坏事件",desc:"缓存诊断检测到的 system prompt / tools 变更等事件"},{key:"usage_stats",label:"原生用量记录",desc:"AstrBot 内置 ProviderStat 表(全量 token 用量记录)"},{key:"ai_diag",label:"AI 诊断缓存",desc:"上次 AI 诊断的结论缓存文件"}];A.useEffect(()=>{t.data&&(n(JSON.parse(JSON.stringify(t.data))),a(!0))},[t.data]);const{status:T,error:N}=Ym(r,async M=>{await fe.postSaveConfig(M);const I=t.data?.currency_symbol;M.currency_symbol!==I&&e&&e()},{enabled:i});if(t.loading&&!t.data)return p.jsx(Xr,{});if(t.error)return p.jsx(Tn,{message:`加载设置失败:${t.error}`});const k=(M,I,X,J)=>{n(te=>{const me={...te};let Re=J;if(X==="bool"?Re=!!J:X==="int"?Re=Math.max(0,parseInt(String(J),10)||0):X==="csv"?Re=String(J).split(",").map(ze=>ze.trim()).filter(Boolean):Re=String(J),M==="_master")me[I]=Re;else{const ze=me[M]||{};me[M]={...ze,[I]:Re}}return me})},R=async()=>{l("执行中…");try{const M=await fe.postCleanup();l(`已清理 ${pe(M.deleted||0)} 条记录`)}catch(M){l(`失败:${M instanceof Error?M.message:String(M)}`)}},C=async()=>{l("执行中…");try{await fe.postReport(),l("日报已触发推送")}catch(M){l(`失败:${M instanceof Error?M.message:String(M)}`)}},D=async()=>{u(!0),c("正在同步…");try{const M=await fe.postSyncRates();c(`已同步 ${M.count} 种货币汇率(${M.exchange_rates_updated_at||"?"})`);const I=await fe.getConfig();n(JSON.parse(JSON.stringify(I)))}catch(M){c(`同步失败:${M instanceof Error?M.message:String(M)}`)}finally{u(!1)}},U=r.exchange_rates||{},W=String(r.exchange_rates_updated_at||""),$=Object.entries(U).filter(([M])=>M!=="USD").sort((M,I)=>M[0].localeCompare(I[0])),L=M=>{g(I=>{const X=new Set(I);return X.has(M)?X.delete(M):X.add(M),X})},F=async()=>{if(v.size!==0){if(!S){b(!0),x("⚠ 再次点击以确认清空,此操作不可恢复"),_.current&&clearTimeout(_.current),_.current=setTimeout(()=>{b(!1),x("")},4e3);return}_.current&&clearTimeout(_.current),b(!1),y(!0),x("正在清空…");try{const M=await fe.postPurge(Array.from(v)),I=Object.entries(M.results).map(([X,J])=>`${P.find(te=>te.key===X)?.label||X}: ${J} 条`);x(`✅ 已清空 — ${I.join(",")}`),g(new Set)}catch(M){x(`❌ 清空失败:${M instanceof Error?M.message:String(M)}`)}finally{y(!1)}}};return p.jsxs("div",{className:"settings-view",children:[p.jsx("div",{className:"settings-hint",children:"此处为插件的全部详细配置,修改后自动保存、即时热生效(无需重载)。预算阈值与模型单价请在「预算」「定价」标签页调整。"}),Pq.map(M=>p.jsxs(Ne,{className:M.key==="advanced"?"panel-advanced":void 0,children:[p.jsxs("h2",{children:[M.title,M.key==="advanced"&&p.jsx("span",{className:"badge-advanced",children:"高级"})]}),M.desc&&p.jsx("p",{className:"section-desc",children:M.desc}),p.jsx("div",{className:"set-fields",children:M.fields.map(I=>{const X=I.group??M.key,J=s1(r,X,I.k);return I.type==="bool"?p.jsxs("div",{className:"set-field",children:[p.jsxs("div",{className:"set-field-text",children:[p.jsx("div",{className:"set-field-label",children:I.label}),I.help&&p.jsx("div",{className:"set-field-help",children:I.help})]}),p.jsxs("label",{className:"switch set-field-control",children:[p.jsx("input",{type:"checkbox",checked:!!J,onChange:te=>k(X,I.k,"bool",te.target.checked)}),p.jsx("span",{className:"slider"})]})]},I.k):I.type==="csv"?p.jsx("div",{className:"set-field",children:p.jsxs("div",{className:"set-field-text",children:[p.jsx("div",{className:"set-field-label",children:I.label}),I.help&&p.jsx("div",{className:"set-field-help",children:I.help}),p.jsx("input",{type:"text",className:"budget-input set-csv-input",defaultValue:Array.isArray(J)?J.join(", "):String(J||""),onBlur:te=>k(X,I.k,"csv",te.target.value)})]})},I.k):I.type==="select"?p.jsxs("div",{className:"set-field",children:[p.jsxs("div",{className:"set-field-text",children:[p.jsx("div",{className:"set-field-label",children:I.label}),I.help&&p.jsx("div",{className:"set-field-help",children:I.help})]}),p.jsx("select",{className:"budget-input set-field-control",value:String(J||""),onChange:te=>k(X,I.k,"select",te.target.value),style:{width:I.width??140},children:(I.options||[]).map(te=>p.jsxs("option",{value:te,children:[te," (",Or(te),")"]},te))})]},I.k):I.type==="dynamic-select"?p.jsxs("div",{className:"set-field",children:[p.jsxs("div",{className:"set-field-text",children:[p.jsx("div",{className:"set-field-label",children:I.label}),I.help&&p.jsx("div",{className:"set-field-help",children:I.help})]}),p.jsxs("select",{className:"budget-input set-field-control",value:String(J||""),onChange:te=>k(X,I.k,"dynamic-select",te.target.value),style:{width:I.width??220},children:[p.jsx("option",{value:"",children:"默认 Provider(AstrBot)"}),O.map(te=>p.jsx("option",{value:te.id,children:te.name},te.id))]})]},I.k):p.jsxs("div",{className:"set-field",children:[p.jsxs("div",{className:"set-field-text",children:[p.jsx("div",{className:"set-field-label",children:I.label}),I.help&&p.jsx("div",{className:"set-field-help",children:I.help})]}),p.jsx("input",{type:I.type==="int"?"number":"text",className:"budget-input set-field-control",value:J===""?"":String(J),onChange:te=>k(X,I.k,I.type,te.target.value),style:{width:I.width??140}})]},I.k)})})]},M.key)),p.jsxs(Ne,{children:[p.jsx("h2",{children:"数据管理"}),p.jsx("p",{className:"section-desc",children:"数据保留策略与手动清理、推送,或按模块清空全部数据。"}),p.jsx("div",{className:"set-fields",children:p.jsxs("div",{className:"set-field",children:[p.jsxs("div",{className:"set-field-text",children:[p.jsx("div",{className:"set-field-label",children:"历史保留天数"}),p.jsx("div",{className:"set-field-help",children:"补充记录的保留天数,到期后定时自动清理;0 = 永不清理。下方「清理过期数据」按此天数立即清理一次。"})]}),p.jsx("input",{type:"number",className:"budget-input set-field-control",value:String(s1(r,"schedule","retain_days")),onChange:M=>k("schedule","retain_days","int",M.target.value),style:{width:100}})]})}),p.jsxs("div",{className:"data-mgmt-section",children:[p.jsx("div",{className:"data-mgmt-label",children:"快速操作"}),p.jsxs("div",{className:"row",children:[p.jsx(xo,{onClick:R,children:"清理过期数据"}),p.jsx(xo,{onClick:C,children:"推送日报"})]}),o&&p.jsx("div",{className:"muted",style:{marginTop:6},children:o})]}),p.jsx("div",{className:"data-mgmt-divider"}),p.jsxs("div",{className:"data-mgmt-section",children:[p.jsx("div",{className:"data-mgmt-label",children:"按模块清空(不可恢复)"}),p.jsx("div",{className:"purge-list",children:P.map(M=>p.jsxs("label",{className:"purge-item",children:[p.jsx("input",{type:"checkbox",checked:v.has(M.key),onChange:()=>L(M.key)}),p.jsxs("div",{className:"purge-item-text",children:[p.jsx("div",{className:"purge-item-label",children:M.label}),p.jsx("div",{className:"purge-item-desc",children:M.desc})]})]},M.key))}),p.jsx("div",{className:"row",style:{marginTop:8},children:p.jsx(xo,{onClick:F,disabled:v.size===0||w,variant:"danger",children:w?"清空中…":S?"⚠ 确认清空":`清空选中模块(${v.size})`})}),m&&p.jsx("div",{className:"muted",style:{marginTop:6},children:m})]})]}),p.jsxs(Ne,{children:[p.jsx("h2",{children:"汇率同步"}),p.jsx("p",{className:"section-desc",children:"点击「立即同步」从免费 API(open.er-api.com)刷新最新汇率。同步后所有费用将按新汇率换算到主货币。无网时使用内置静态汇率兜底。"}),p.jsxs("div",{className:"row",children:[p.jsx(xo,{onClick:D,disabled:s,children:s?"同步中…":"立即同步汇率"}),W&&p.jsxs("span",{className:"muted",style:{alignSelf:"center"},children:["上次同步:",W]})]}),f&&p.jsx("div",{className:"muted",style:{marginTop:8},children:f}),$.length>0&&p.jsxs("div",{className:"rate-disclosure",children:[p.jsxs("button",{type:"button",className:"rate-toggle",onClick:()=>h(M=>!M),"aria-expanded":d,children:[p.jsx("span",{className:"rate-toggle-caret",children:d?"▾":"▸"}),"查看 ",$.length," 个汇率"]}),d&&p.jsx("div",{className:"rate-grid",style:{marginTop:10},children:$.map(([M,I])=>p.jsxs("div",{className:"rate-item",children:[p.jsx("span",{className:"rate-code",children:M}),p.jsx("span",{className:"rate-value",children:I.toFixed(4)})]},M))})]})]}),p.jsx(Qm,{status:T,error:N})]})}const u1=[{key:"overview",label:"总览"},{key:"records",label:"明细"},{key:"budgets",label:"预算"},{key:"cache",label:"缓存"},{key:"attribution",label:"上下文"},{key:"pricing",label:"定价"},{key:"settings",label:"设置"}];function Eq(){const{ready:e,ctx:t,failed:r}=ME();IE(t);const n=RE(!!t?.isDark),[i,a]=A.useState("overview"),[o,l]=A.useState("weekly"),[s,u]=A.useState(0);A.useEffect(()=>{e&&fe.getConfig().then(v=>{const g=v?.currency_symbol;typeof g=="string"&&g&&ug(g)}).catch(()=>{})},[e,s]);const f=()=>u(v=>v+1),c=A.useCallback(async()=>{try{const g=(await fe.getConfig())?.currency_symbol;typeof g=="string"&&g&&ug(g)}catch{}u(v=>v+1)},[]),d=t?`${t.displayName||"插件"} · ${t.locale||""}`:"",h=r?"未连接":e?"已连接":"连接中…";return p.jsxs(p.Fragment,{children:[p.jsxs("header",{className:"topbar",children:[p.jsxs("div",{className:"title-group",children:[p.jsx("h1",{children:"成本控制"}),(i==="overview"||i==="attribution"||i==="cache")&&p.jsx(fu,{value:o,onChange:v=>l(v),options:[{value:"daily",label:"日"},{value:"weekly",label:"周"},{value:"monthly",label:"月"}]})]}),p.jsxs("div",{className:"topbar-right",children:[p.jsx("span",{className:"status",children:h}),p.jsx("button",{className:"btn",title:"刷新",onClick:f,children:"↻"})]})]}),p.jsx("nav",{className:"tabs",children:u1.map(v=>p.jsx("button",{className:`tab ${v.key===i?"active":""}`.trim(),onClick:()=>a(v.key),children:v.label},v.key))}),p.jsx("main",{className:"content",children:r?p.jsx(Tn,{message:"bridge SDK 未注入(请在 AstrBot WebUI 插件页打开本页面)"}):e?i==="overview"?p.jsx(QX,{window:o,refreshNonce:s,colors:n,onNavigate:v=>a(v)}):i==="records"?p.jsx(eq,{refreshNonce:s}):i==="budgets"?p.jsx(cq,{refreshNonce:s}):i==="cache"?p.jsx(yq,{window:o,refreshNonce:s}):i==="attribution"?p.jsx(gq,{window:o,refreshNonce:s}):i==="pricing"?p.jsx(jq,{refreshNonce:s}):i==="settings"?p.jsx(Aq,{onCurrencyChanged:c}):p.jsxs("div",{className:"empty",children:["「",u1.find(v=>v.key===i)?.label,"」开发中…"]}):p.jsx(Xr,{})}),p.jsx("footer",{className:"footer",children:d})]})}const c1=document.getElementById("root");c1&&_S(c1).render(p.jsx(A.StrictMode,{children:p.jsx(Eq,{})})); diff --git a/pages/dashboard/index.html b/pages/dashboard/index.html index 9972385..2145448 100644 --- a/pages/dashboard/index.html +++ b/pages/dashboard/index.html @@ -1,18 +1,18 @@ - - - - - - 成本控制 - + + + + + + 成本控制 + - - -
- + + +
+ - - - + + + From 8e531b36c15559e604b433067665ea577974f2f1 Mon Sep 17 00:00:00 2001 From: Rain-0x01-39 <83620631+Rain-0x01-39@users.noreply.github.com> Date: Wed, 26 Aug 2026 22:35:34 +0800 Subject: [PATCH 09/11] =?UTF-8?q?chore:=20=E6=B7=BB=E5=8A=A0=20.gitattribu?= =?UTF-8?q?tes=20=E8=A7=84=E8=8C=83=E5=8C=96=E6=9E=84=E5=BB=BA=E4=BA=A7?= =?UTF-8?q?=E7=89=A9=E6=8D=A2=E8=A1=8C=E5=B9=B6=E6=8A=98=E5=8F=A0=20PR=20d?= =?UTF-8?q?iff?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitattributes | 6 ++++++ pages/dashboard/index.html | 30 +++++++++++++++--------------- 2 files changed, 21 insertions(+), 15 deletions(-) create mode 100644 .gitattributes diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..cc1bff1 --- /dev/null +++ b/.gitattributes @@ -0,0 +1,6 @@ +# 前端构建产物:PR diff 默认折叠(linguist-generated),统一 LF 消除跨平台换行噪音 +pages/dashboard/* linguist-generated text eol=lf + +# 锁文件不参与代码统计 +uv.lock linguist-generated +frontend/package-lock.json linguist-generated \ No newline at end of file diff --git a/pages/dashboard/index.html b/pages/dashboard/index.html index 2145448..e5346d7 100644 --- a/pages/dashboard/index.html +++ b/pages/dashboard/index.html @@ -1,18 +1,18 @@ - - - - - - 成本控制 - + + + + + + 成本控制 + - - -
- + + +
+ - - - + + + From 541dc8fa89869987d5ac8dad46e6bd300fb05f0a Mon Sep 17 00:00:00 2001 From: Rain-0x01-39 <83620631+Rain-0x01-39@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:44:58 +0800 Subject: [PATCH 10/11] =?UTF-8?q?fix:=20=E4=BF=AE=E5=A4=8D=E5=89=A9?= =?UTF-8?q?=E4=BD=99=E8=B4=A7=E5=B8=81=E5=8F=A3=E5=BE=84=E9=97=AE=E9=A2=98?= =?UTF-8?q?=EF=BC=88M2/M4/L2/L3/L4=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - M2: /budget 命令的全局维度与 override 花费限额按 budgets_cost_currency / cost_currency 换算到主货币后再显示(此前用主货币符号配原始货币值)。 - M4: 未定价模型告警文案硬编码 $0 改为跟随主货币符号。 - L2/L3: budget._override_used 与 web_api._supplement_to_dict 默认参数 main_cur="$"(非法货币代码)改为 "USD"。 - L4: ai_diag 喂 LLM 的预算数据 cost_limit 换算到主货币口径,并补充 cost_used / cost_ratio 与 cost 维度超限判断(此前 exceeded 只判 token)。 --- cost_control/ai_diag.py | 48 ++++++++++++++++++++++++++++++++++------ cost_control/budget.py | 26 +++++----------------- cost_control/commands.py | 26 +++++++++++++++++----- cost_control/web_api.py | 14 +++++++----- 4 files changed, 75 insertions(+), 39 deletions(-) diff --git a/cost_control/ai_diag.py b/cost_control/ai_diag.py index 950c8df..8641e07 100644 --- a/cost_control/ai_diag.py +++ b/cost_control/ai_diag.py @@ -267,6 +267,28 @@ async def _collect_diag_data(self) -> dict[str, Any]: limits = self.get_budgets() limits_cost = self.get_budgets_cost() + # 预算 cost 维度:限额与花费均换算到主货币口径(与 /budgets、/alerts 一致) + from .config import get_budgets_cost_currency + from .cost import compute_cost_grouped_in_main + from .exchange_rates import convert, get_main_currency, get_rates + + _main_cur = get_main_currency(getattr(self, "cfg", None)) + _rates = get_rates(getattr(self, "cfg", None)) + _bcc = get_budgets_cost_currency(getattr(self, "cfg", None)) + _pricing = self.get_pricing() + day_cost_c = compute_cost_grouped_in_main( + await self.query_usage_grouped(by="provider_model", start=d_start), + _pricing, + _main_cur, + _rates, + ) + month_cost_c = compute_cost_grouped_in_main( + await self.query_usage_grouped(by="provider_model", start=m_start), + _pricing, + _main_cur, + _rates, + ) + dims: list[dict[str, Any]] = [] dim_labels = { "global_daily": "每日全局", @@ -276,20 +298,32 @@ async def _collect_diag_data(self) -> dict[str, Any]: "per_model_daily": "每模型·每日", } dim_used = {"global_daily": day_total, "global_monthly": month_total} + dim_used_c = {"global_daily": day_cost_c, "global_monthly": month_cost_c} for d in _DIM_ORDER: lt = int(limits.get(d, 0) or 0) - lc = float(limits_cost.get(d, 0) or 0) - used = dim_used.get(d, 0) - if lt > 0 or lc > 0: - ratio = round(used * 100.0 / lt, 1) if lt > 0 else 0 + lc_raw = float(limits_cost.get(d, 0) or 0) + used_t = dim_used.get(d, 0) + used_c = dim_used_c.get(d, 0) + if lt > 0 or lc_raw > 0: + d_cur = str(_bcc.get(d, "") or "") or _main_cur + lc = ( + round(convert(lc_raw, d_cur, _main_cur, _rates), 6) + if lc_raw > 0 and d_cur != _main_cur + else lc_raw + ) + ratio_t = round(used_t * 100.0 / lt, 1) if lt > 0 else 0 + ratio_c = round(used_c * 100.0 / lc, 1) if lc > 0 else 0 dims.append( { "dimension": dim_labels.get(d, d), "token_limit": lt, - "token_used": used, - "token_ratio": ratio, + "token_used": used_t, + "token_ratio": ratio_t, "cost_limit": lc, - "exceeded": used >= lt if lt > 0 else False, + "cost_used": round(used_c, 6), + "cost_ratio": ratio_c, + "exceeded": (used_t >= lt if lt > 0 else False) + or (used_c >= lc if lc > 0 else False), } ) data["budgets"] = {"dimensions": dims} diff --git a/cost_control/budget.py b/cost_control/budget.py index cde19a4..1e14334 100644 --- a/cost_control/budget.py +++ b/cost_control/budget.py @@ -33,7 +33,6 @@ from .attributor import _str_tokens from .config import enabled_overrides, get_config -from .cost import compute_row_cost def resolve_tz(context: Any) -> ZoneInfo: @@ -132,24 +131,6 @@ def check_dimensions_dual( return {"exceeded": False, "dim": None, "metric": None, "limit": 0.0, "used": 0.0} -def _groups_cost( - groups: list[dict[str, Any]], - pricing: dict[str, Any], -) -> float: - """把 ``query_usage_grouped(by="provider_model")`` 结果按行求和花费(纯函数)。 - - 每行含 ``provider_id`` / ``provider_model`` / ``count`` / token 字段,委托 - :func:`cost_control.cost.compute_row_cost`(按 provider_id 匹配用户定价、按 mode 计费)。 - """ - total = 0.0 - for g in groups or []: - try: - total += compute_row_cost(g, pricing) - except Exception: - continue - return round(total, 6) - - def total_tokens(usage: dict[str, Any]) -> int: """把三类 token 聚合为总数(纯函数)。""" return ( @@ -327,7 +308,7 @@ async def _override_used( provider_id: str | None, d_start: datetime, pricing: dict[str, Any], - main_cur: str = "$", + main_cur: str = "USD", rates: dict[str, float] | None = None, ) -> float: """按 override target 聚合当前周期的 ``metric`` 用量(token 数 / 主货币花费)。 @@ -679,7 +660,10 @@ def _format_message(self, result: dict[str, Any]) -> str: cur = str(result.get("currency") or "") or get_main_currency(getattr(self, "cfg", None)) sym = currency_to_symbol(cur) - return f"⏸ 已超出花费预算({dim}):{sym}{float(used or 0):.4f} / {sym}{float(limit or 0):.2f}" + return ( + f"⏸ 已超出花费预算({dim}):{sym}{float(used or 0):.4f} / " + f"{sym}{float(limit or 0):.2f}" + ) # token 维度的 used/limit 来自 float() 包装,转 int 避免显示 "150.0"。 return f"⏸ 已超出预算({dim}):用 {int(used or 0)} / 限 {int(limit or 0)} token" diff --git a/cost_control/commands.py b/cost_control/commands.py index a22d55b..73b56e5 100644 --- a/cost_control/commands.py +++ b/cost_control/commands.py @@ -22,9 +22,9 @@ from .attributor import ESTIMATION_NOTE from .budget import _DIM_ORDER, day_window_start, resolve_tz -from .config import get_config +from .config import get_budgets_cost_currency, get_config from .cost import compute_row_cost_in_main -from .exchange_rates import currency_to_symbol, get_main_currency, get_rates +from .exchange_rates import convert, currency_to_symbol, get_main_currency, get_rates # 插件主模块路径(``main.py``)。AstrBot 的 ``star_map`` 以 ``Main.__module__`` # 为键,而 ``update_command_permission`` 等管理接口通过 ``handler.__module__`` @@ -111,7 +111,10 @@ async def cmd_budget(self, event: AstrMessageEvent): """``/budget``:查询预算配置与当前超限状态。""" try: umo = self._umo(event) - sym = currency_to_symbol(get_main_currency(getattr(self, "cfg", None))) + main_cur = get_main_currency(getattr(self, "cfg", None)) + rates = get_rates(getattr(self, "cfg", None)) + bcc = get_budgets_cost_currency(getattr(self, "cfg", None)) + sym = currency_to_symbol(main_cur) budgets = self.get_budgets() budgets_cost = self.get_budgets_cost() overrides = self.get_budget_overrides(getattr(self, "cfg", None)) @@ -126,7 +129,13 @@ async def cmd_budget(self, event: AstrMessageEvent): if t > 0: parts.append(f"token {t}") if c > 0: - parts.append(f"花费 {sym}{c:.2f}") + dim_cur = str(bcc.get(dim, "") or "") or main_cur + c_main = ( + round(convert(c, dim_cur, main_cur, rates), 6) + if dim_cur != main_cur + else c + ) + parts.append(f"花费 {sym}{c_main:.2f}") lines.append(f" {dim}: " + " / ".join(parts)) any_cfg = True if not any_cfg: @@ -138,7 +147,14 @@ async def cmd_budget(self, event: AstrMessageEvent): if ov.get("token_limit", 0) > 0: parts.append(f"token {ov['token_limit']}") if ov.get("cost_limit", 0) > 0: - parts.append(f"花费 {sym}{ov['cost_limit']:.2f}") + ov_cur = str(ov.get("cost_currency") or "") or main_cur + ov_c = float(ov.get("cost_limit") or 0) + ov_c_main = ( + round(convert(ov_c, ov_cur, main_cur, rates), 6) + if ov_cur != main_cur + else ov_c + ) + parts.append(f"花费 {sym}{ov_c_main:.2f}") lines.append( f" · {ov.get('target_type')}:{ov.get('target_value')} " f"({'/'.join(parts) or '不限'}) " diff --git a/cost_control/web_api.py b/cost_control/web_api.py index c293bd6..cec4e3e 100644 --- a/cost_control/web_api.py +++ b/cost_control/web_api.py @@ -283,7 +283,7 @@ def _parse_iso(s: str | None, *, is_end: bool = False) -> datetime | None: def _supplement_to_dict( s: Any, pricing: dict[str, Any] | None = None, - main_cur: str = "$", + main_cur: str = "USD", rates: dict[str, float] | None = None, ) -> dict[str, Any]: """把 ``CostSupplement`` 行序列化为 JSON 友好 dict。 @@ -499,6 +499,9 @@ async def api_alerts(self, **kwargs: Any) -> dict[str, Any]: + int(r.get("token_output", 0) or 0) ) if unpriced_count > 0: + from .exchange_rates import currency_to_symbol, get_main_currency + + _unpriced_sym = currency_to_symbol(get_main_currency(getattr(self, "cfg", None))) alerts.append( { "level": "warn", @@ -506,7 +509,8 @@ async def api_alerts(self, **kwargs: Any) -> dict[str, Any]: "title": "存在未定价模型", "detail": ( f"检测到 {unpriced_count} 个模型未配置定价" - f"(涉及 {unpriced_tokens} token 用量),其成本被计为 $0," + f"(涉及 {unpriced_tokens} token 用量),其成本被计为 " + f"{_unpriced_sym}0," "导致成本统计偏低。请前往定价页为对应 provider 设置单价。" ), "tab": "pricing", @@ -559,7 +563,8 @@ async def api_alerts(self, **kwargs: Any) -> dict[str, Any]: # 将各维度 cost 限额换算到主货币(budgets_cost_currency 可能设了独立货币) from .config import get_budgets_cost_currency - from .exchange_rates import convert as _conv_alert, get_main_currency, get_rates + from .exchange_rates import convert as _conv_alert + from .exchange_rates import get_main_currency, get_rates _alert_bcc = get_budgets_cost_currency(cfg) _alert_main = get_main_currency(cfg) @@ -641,7 +646,6 @@ async def api_compare(self, **kwargs: Any) -> dict[str, Any]: from .analytics import compare_windows from .budget import total_tokens - from .cost import compute_cost_grouped window = self._param("window", "daily") or "daily" now = datetime.now(UTC) @@ -804,8 +808,6 @@ async def api_records_aggregate(self, **kwargs: Any) -> dict[str, Any]: (可选)、``start`` / ``end``(ISO)。返回每组的 token 三类、条数、成本、占比。 """ try: - from .cost import compute_row_cost - by = self._param("by", "model") or "model" if by not in ("model", "provider", "umo"): by = "model" From f0c929ca019c3082c4dffe81b61cc80c7553f07f Mon Sep 17 00:00:00 2001 From: Rain-0x01-39 <83620631+Rain-0x01-39@users.noreply.github.com> Date: Thu, 27 Aug 2026 17:45:05 +0800 Subject: [PATCH 11/11] =?UTF-8?q?refactor:=20=E6=B8=85=E7=90=86=E5=8E=9F?= =?UTF-8?q?=E5=A7=8B=E5=8F=A3=E5=BE=84=E6=AD=BB=E4=BB=A3=E7=A0=81=E4=B8=8E?= =?UTF-8?q?=E6=AD=BB=E5=AF=BC=E5=85=A5=EF=BC=88L1=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 删除 budget._groups_cost(仅测试引用,生产已统一走 compute_cost_grouped_in_main) 及相关 3 个单测。 - 删除 analytics._row_cost(无任何调用方)与 compute_row_cost 死导入。 - 删除 cost.CostMixin.compute_cost(async,无调用方)。 - 删除 web_api 内 compute_cost_grouped / compute_row_cost 死导入(同函数内 实际使用 _in_main 版本)。 避免原始计费货币口径函数被后续误用。 --- cost_control/analytics.py | 7 +------ cost_control/cost.py | 18 ----------------- tests/test_budget.py | 42 --------------------------------------- 3 files changed, 1 insertion(+), 66 deletions(-) diff --git a/cost_control/analytics.py b/cost_control/analytics.py index 0b1bf92..8cf09a4 100644 --- a/cost_control/analytics.py +++ b/cost_control/analytics.py @@ -19,7 +19,7 @@ from .budget import day_window_start, resolve_tz from .cache_diag import hit_rate from .config import get_config -from .cost import compute_cost_in_main, compute_row_cost, compute_row_cost_in_main +from .cost import compute_cost_in_main, compute_row_cost_in_main def report_window_start( @@ -82,11 +82,6 @@ def compare_windows( return cur_start, cur_end, prev_start, prev_end -def _row_cost(row: dict[str, Any], pricing: dict[str, Any]) -> float: - """按 (provider_id, model) 解析定价算单行成本(纯函数辅助)。无定价返回 0.0。""" - return compute_row_cost(row, pricing) - - def _row_cost_in_main( row: dict[str, Any], pricing: dict[str, Any], diff --git a/cost_control/cost.py b/cost_control/cost.py index a024a36..1eb4ada 100644 --- a/cost_control/cost.py +++ b/cost_control/cost.py @@ -436,21 +436,3 @@ def get_rates(self) -> dict[str, float]: from .config import get_rates return get_rates(getattr(self, "cfg", None)) - - async def compute_cost( - self, - usage: dict[str, Any], - provider_id: str | None, - model: str | None, - ) -> float: - """按生效定价把单条 usage 换算为 USD 成本。 - - Args: - usage: 聚合用量 dict。 - provider_id: Provider ID(用户定价匹配)。 - model: 模型名(默认表匹配)。 - - Returns: - USD 成本(float)。 - """ - return compute_cost_value(usage, provider_id, model, self.get_pricing()) diff --git a/tests/test_budget.py b/tests/test_budget.py index db8626e..f2f74ee 100644 --- a/tests/test_budget.py +++ b/tests/test_budget.py @@ -8,7 +8,6 @@ from cost_control.budget import ( BudgetMixin, - _groups_cost, check_dimensions, check_dimensions_dual, day_window_start, @@ -22,7 +21,6 @@ truncate_contexts, ) from cost_control.config import ( - DEFAULT_PRICING, enabled_fallback_providers, enabled_overrides, normalize_budget_override, @@ -366,46 +364,6 @@ def test_check_dimensions_dual_zero_limits_skipped(): assert r["exceeded"] is False -def test_groups_cost_multi_model(): - pricing = {"defaults": DEFAULT_PRICING, "user": {}} - groups = [ - { - "provider_id": None, - "provider_model": "gpt-4o", - "token_input_other": 1_000_000, - "token_input_cached": 0, - "token_output": 0, - }, - { - "provider_id": None, - "provider_model": "gpt-4o-mini", - "token_input_other": 1_000_000, - "token_input_cached": 0, - "token_output": 0, - }, - ] - # gpt-4o 1M input = $2.5;gpt-4o-mini 1M input = $0.15 → 合计 $2.65 - assert abs(_groups_cost(groups, pricing) - 2.65) < 1e-6 - - -def test_groups_cost_unpriced_zero(): - pricing = {"defaults": DEFAULT_PRICING, "user": {}} - groups = [ - { - "provider_id": None, - "provider_model": "nonexistent-xyz", - "token_input_other": 1_000_000, - "token_input_cached": 0, - "token_output": 0, - }, - ] - assert _groups_cost(groups, pricing) == 0.0 - - -def test_groups_cost_empty(): - assert _groups_cost([], {"defaults": DEFAULT_PRICING, "user": {}}) == 0.0 - - class _BudgetStub(BudgetMixin): """仅带 config 的 BudgetMixin 实例(测 get_budgets_cost,不触 DB/context)。"""