coding-proxy 0.2.3a3__tar.gz → 0.2.3a5__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/PKG-INFO +1 -1
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/pyproject.toml +1 -1
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/config/config.default.yaml +1 -1
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/logging/db.py +1 -1
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/routing/executor.py +6 -1
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/routing/quota_guard.py +1 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/server/dashboard.py +161 -95
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/uv.lock +1 -1
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/.github/workflows/ci.yml +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/.github/workflows/coverage.yml +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/.github/workflows/release.yml +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/.gitignore +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/AGENTS.md +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/CHANGELOG.md +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/CLAUDE.md +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/LICENSE +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/README.md +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/docs/ci-cd.md +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/docs/framework.md +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/docs/user-guide.md +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/docs/zh-CN/README.md +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/__main__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/auth/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/auth/providers/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/auth/providers/base.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/auth/providers/github.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/auth/providers/google.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/auth/runtime.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/auth/store.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/cli/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/cli/auth_commands.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/cli/banner.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/compat/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/compat/canonical.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/compat/session_store.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/config/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/config/auth_schema.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/config/loader.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/config/resiliency.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/config/routing.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/config/schema.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/config/server.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/config/vendors.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/convert/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/convert/anthropic_to_gemini.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/convert/anthropic_to_openai.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/convert/gemini_sse_adapter.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/convert/gemini_to_anthropic.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/convert/openai_to_anthropic.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/logging/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/logging/formatters.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/logging/stats.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/model/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/model/auth.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/model/compat.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/model/constants.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/model/pricing.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/model/token.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/model/vendor.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/pricing.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/routing/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/routing/circuit_breaker.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/routing/error_classifier.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/routing/model_mapper.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/routing/rate_limit.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/routing/retry.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/routing/router.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/routing/session_manager.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/routing/tier.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/routing/usage_parser.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/routing/usage_recorder.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/server/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/server/app.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/server/factory.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/server/request_normalizer.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/server/responses.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/server/routes.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/streaming/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/streaming/anthropic_compat.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/alibaba.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/anthropic.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/antigravity.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/base.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/copilot.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/copilot_models.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/copilot_token_manager.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/copilot_urls.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/doubao.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/kimi.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/minimax.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/mixins.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/native_anthropic.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/token_manager.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/xiaomi.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/zhipu.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_antigravity.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_app_routes.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_auto_login.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_banner.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_circuit_breaker.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_cli_usage.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_compat.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_config_init.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_config_loader.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_convert_request.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_convert_response.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_convert_sse.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_copilot.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_copilot_convert_request.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_copilot_convert_response.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_copilot_models.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_copilot_urls.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_currency.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_error_classifier.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_logging_dual_write.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_mixins.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_model_auth.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_model_compat.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_model_constants.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_model_mapper.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_model_pricing.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_model_token.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_model_vendor.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_native_vendors.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_parse_usage.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_pricing.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_quota_guard.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_rate_limit.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_request_normalizer.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_router_chain.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_router_executor.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_runtime_reauth.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_schema.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_streaming_anthropic_compat.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_tier.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_tiers_config.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_time_range.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_token_logger.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_token_manager.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_types.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_vendor_streaming.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_vendors.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/tests/test_zhipu.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: coding-proxy
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.3a5
|
|
4
4
|
Summary: A High-Availability, Transparent, and Smart Multi-Vendor Proxy for Claude Code. Support Claude Plans, GitHub Copilot, Google Antigravity, ZAI/GLM, MiniMax, Qwen, Xiaomi, Kimi, Doubao...
|
|
5
5
|
Project-URL: Source Code, https://github.com/ThreeFish-AI/coding-proxy
|
|
6
6
|
Project-URL: User Guide, https://github.com/ThreeFish-AI/coding-proxy/blob/master/docs/user-guide.md
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "coding-proxy"
|
|
3
|
-
version = "0.2.
|
|
3
|
+
version = "0.2.3a5"
|
|
4
4
|
description = "A High-Availability, Transparent, and Smart Multi-Vendor Proxy for Claude Code. Support Claude Plans, GitHub Copilot, Google Antigravity, ZAI/GLM, MiniMax, Qwen, Xiaomi, Kimi, Doubao..."
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -58,7 +58,7 @@ vendors:
|
|
|
58
58
|
probe_interval_seconds: 300
|
|
59
59
|
weekly_quota_guard:
|
|
60
60
|
enabled: true
|
|
61
|
-
token_budget:
|
|
61
|
+
token_budget: 500000000 # 一周 token 预算(根据订阅计划调整)
|
|
62
62
|
window_hours: 168.0 # 7 天滑动窗口
|
|
63
63
|
threshold_percent: 99.0
|
|
64
64
|
probe_interval_seconds: 1800 # 每 30 分钟探测一次
|
|
@@ -497,7 +497,7 @@ class TokenLogger:
|
|
|
497
497
|
return 0
|
|
498
498
|
cutoff_iso = _hours_ago_utc_iso(window_hours)
|
|
499
499
|
cursor = await self._db.execute(
|
|
500
|
-
"""SELECT COALESCE(SUM(input_tokens + output_tokens), 0) AS total
|
|
500
|
+
"""SELECT COALESCE(SUM(input_tokens + output_tokens + cache_creation_tokens + cache_read_tokens), 0) AS total
|
|
501
501
|
FROM usage_log
|
|
502
502
|
WHERE vendor = ? AND success = 1
|
|
503
503
|
AND ts >= ?""",
|
|
@@ -276,7 +276,12 @@ class _RouteExecutor:
|
|
|
276
276
|
tier.name,
|
|
277
277
|
usage,
|
|
278
278
|
)
|
|
279
|
-
tier.record_success(
|
|
279
|
+
tier.record_success(
|
|
280
|
+
info.input_tokens
|
|
281
|
+
+ info.output_tokens
|
|
282
|
+
+ info.cache_creation_tokens
|
|
283
|
+
+ info.cache_read_tokens
|
|
284
|
+
)
|
|
280
285
|
duration = int((time.monotonic() - start) * 1000)
|
|
281
286
|
model = body.get("model", "unknown")
|
|
282
287
|
model_served = usage.get("model_served") or tier.vendor.map_model(model)
|
|
@@ -19,14 +19,20 @@ def _build_favicon() -> bytes:
|
|
|
19
19
|
|
|
20
20
|
width, height = 16, 16
|
|
21
21
|
pixel_rows: list[bytes] = []
|
|
22
|
+
cx, cy = width / 2.0, height / 2.0
|
|
22
23
|
for y in range(height - 1, -1, -1): # BMP bottom-up
|
|
23
24
|
row = bytearray()
|
|
24
25
|
for x in range(width):
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
26
|
+
dx = x - cx + 0.5
|
|
27
|
+
dy = y - cy + 0.5
|
|
28
|
+
if dx * dx + dy * dy > (width / 2.0) ** 2:
|
|
29
|
+
row.extend([0, 0, 0, 0]) # 圆外透明
|
|
30
|
+
else:
|
|
31
|
+
t = (x + (height - 1 - y)) / (width + height - 2)
|
|
32
|
+
r = int(88 + (188 - 88) * t)
|
|
33
|
+
g = int(166 + (140 - 166) * t)
|
|
34
|
+
b = 255
|
|
35
|
+
row.extend([b, g, r, 255]) # BGRA
|
|
30
36
|
pixel_rows.append(bytes(row))
|
|
31
37
|
|
|
32
38
|
bmp_hdr = struct.pack(
|
|
@@ -114,7 +120,7 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
114
120
|
.logo {
|
|
115
121
|
width: 30px; height: 30px;
|
|
116
122
|
background: linear-gradient(135deg, var(--accent-blue), var(--accent-purple));
|
|
117
|
-
border-radius:
|
|
123
|
+
border-radius: 50%;
|
|
118
124
|
display: flex; align-items: center; justify-content: center;
|
|
119
125
|
font-size: 15px; font-weight: 700; color: #fff;
|
|
120
126
|
box-shadow: 0 2px 8px rgba(88,166,255,.3);
|
|
@@ -148,7 +154,7 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
148
154
|
/* ── KPI 卡片 ── */
|
|
149
155
|
.kpi-grid {
|
|
150
156
|
display: grid;
|
|
151
|
-
grid-template-columns: repeat(auto-fit, minmax(
|
|
157
|
+
grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));
|
|
152
158
|
gap: 12px;
|
|
153
159
|
margin-bottom: 18px;
|
|
154
160
|
}
|
|
@@ -189,7 +195,7 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
189
195
|
font-family: 'JetBrains Mono', monospace;
|
|
190
196
|
letter-spacing: -0.5px;
|
|
191
197
|
}
|
|
192
|
-
.kpi-sub { font-size: 11px; color: var(--text-tertiary); margin-top: 5px; }
|
|
198
|
+
.kpi-sub { font-size: 11px; color: var(--text-tertiary); margin-top: 5px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 100%; }
|
|
193
199
|
.color-blue { color: var(--accent-blue); }
|
|
194
200
|
.color-green { color: var(--accent-green); }
|
|
195
201
|
.color-yellow { color: var(--accent-yellow); }
|
|
@@ -446,33 +452,32 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
446
452
|
</div>
|
|
447
453
|
</div>
|
|
448
454
|
|
|
449
|
-
<!-- 故障转移明细表 -->
|
|
450
|
-
<div class="card">
|
|
451
|
-
<div class="card-title">故障转移明细</div>
|
|
452
|
-
<div class="ft-table-wrap">
|
|
453
|
-
<table>
|
|
454
|
-
<thead>
|
|
455
|
-
<tr>
|
|
456
|
-
<th>来源供应商</th>
|
|
457
|
-
<th>目标供应商</th>
|
|
458
|
-
<th>次数</th>
|
|
459
|
-
</tr>
|
|
460
|
-
</thead>
|
|
461
|
-
<tbody id="ft-tbody">
|
|
462
|
-
<tr><td colspan="3" class="empty">加载中…</td></tr>
|
|
463
|
-
</tbody>
|
|
464
|
-
</table>
|
|
465
|
-
</div>
|
|
466
|
-
</div>
|
|
467
455
|
</main>
|
|
468
456
|
|
|
469
457
|
<script>
|
|
470
458
|
// ── 颜色配置 ──────────────────────────────────────────────
|
|
459
|
+
// 调色盘参考 Tailwind CSS 400-level,深色背景高区分度最佳实践
|
|
471
460
|
const VENDOR_COLORS = [
|
|
472
|
-
'#
|
|
473
|
-
'#
|
|
474
|
-
'#
|
|
475
|
-
'#
|
|
461
|
+
'#60A5FA', // blue-400
|
|
462
|
+
'#FB923C', // orange-400
|
|
463
|
+
'#34D399', // emerald-400
|
|
464
|
+
'#A78BFA', // violet-400
|
|
465
|
+
'#F87171', // red-400
|
|
466
|
+
'#38BDF8', // sky-400
|
|
467
|
+
'#FBBF24', // amber-400
|
|
468
|
+
'#F472B6', // pink-400
|
|
469
|
+
'#4ADE80', // green-400
|
|
470
|
+
'#E879F9', // fuchsia-400
|
|
471
|
+
'#818CF8', // indigo-400
|
|
472
|
+
'#2DD4BF', // teal-400
|
|
473
|
+
'#FB7185', // rose-400
|
|
474
|
+
'#FCD34D', // yellow-300
|
|
475
|
+
'#6EE7B7', // emerald-300
|
|
476
|
+
'#C4B5FD', // violet-300
|
|
477
|
+
'#7DD3FC', // sky-300
|
|
478
|
+
'#FED7AA', // orange-200
|
|
479
|
+
'#FECDD3', // rose-200
|
|
480
|
+
'#BBF7D0', // green-200
|
|
476
481
|
];
|
|
477
482
|
|
|
478
483
|
// ── 工具函数 ──────────────────────────────────────────────
|
|
@@ -502,12 +507,72 @@ Chart.defaults.color = '#8b949e';
|
|
|
502
507
|
Chart.defaults.borderColor = 'rgba(255,255,255,.04)';
|
|
503
508
|
Chart.defaults.font.family = '-apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif';
|
|
504
509
|
Chart.defaults.font.size = 11;
|
|
510
|
+
Chart.defaults.plugins.tooltip.usePointStyle = true;
|
|
505
511
|
|
|
506
512
|
const COMMON_SCALE_X = { grid: { display: false }, ticks: { maxTicksLimit: 10 } };
|
|
507
513
|
const COMMON_SCALE_Y = { grid: { color: 'rgba(255,255,255,.04)' }, beginAtZero: true };
|
|
508
|
-
const COMMON_LEGEND = {
|
|
514
|
+
const COMMON_LEGEND = {
|
|
515
|
+
position: 'bottom',
|
|
516
|
+
labels: {
|
|
517
|
+
boxWidth: 8,
|
|
518
|
+
padding: 14,
|
|
519
|
+
usePointStyle: true,
|
|
520
|
+
pointStyle: 'circle',
|
|
521
|
+
pointStyleWidth: 8,
|
|
522
|
+
font: { size: 11 },
|
|
523
|
+
generateLabels: chart => {
|
|
524
|
+
const items = Chart.defaults.plugins.legend.labels.generateLabels(chart);
|
|
525
|
+
items.forEach(item => { item.pointStyle = 'circle'; item.lineWidth = 0; item.fillStyle = item.strokeStyle; });
|
|
526
|
+
return items;
|
|
527
|
+
},
|
|
528
|
+
},
|
|
529
|
+
};
|
|
509
530
|
const COMMON_LINE_DATASET = { tension: .35, pointRadius: 0, pointHoverRadius: 5, borderWidth: 2 };
|
|
510
531
|
|
|
532
|
+
// ── Legend 点击交互:单击=仅选该项,Ctrl/Meta+单击=多选追加,Shift+单击=排除 ──
|
|
533
|
+
function legendOnClick(e, legendItem, legend) {
|
|
534
|
+
const chart = legend.chart;
|
|
535
|
+
const isShift = e.native.shiftKey;
|
|
536
|
+
const isCtrl = e.native.ctrlKey || e.native.metaKey;
|
|
537
|
+
if (chart.config.type === 'doughnut' || chart.config.type === 'pie') {
|
|
538
|
+
const idx = legendItem.index;
|
|
539
|
+
const dataLen = chart.data.labels.length;
|
|
540
|
+
if (isShift) {
|
|
541
|
+
chart.toggleDataVisibility(idx);
|
|
542
|
+
} else if (isCtrl) {
|
|
543
|
+
if (!chart.getDataVisibility(idx)) chart.toggleDataVisibility(idx);
|
|
544
|
+
} else {
|
|
545
|
+
const allOthersHidden = [...Array(dataLen).keys()].filter(i => i !== idx).every(i => !chart.getDataVisibility(i));
|
|
546
|
+
if (allOthersHidden) {
|
|
547
|
+
for (let i = 0; i < dataLen; i++) { if (!chart.getDataVisibility(i)) chart.toggleDataVisibility(i); }
|
|
548
|
+
} else {
|
|
549
|
+
for (let i = 0; i < dataLen; i++) {
|
|
550
|
+
const vis = chart.getDataVisibility(i);
|
|
551
|
+
if (i === idx && !vis) chart.toggleDataVisibility(i);
|
|
552
|
+
if (i !== idx && vis) chart.toggleDataVisibility(i);
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
}
|
|
556
|
+
} else {
|
|
557
|
+
const idx = legendItem.datasetIndex;
|
|
558
|
+
const datasets = chart.data.datasets;
|
|
559
|
+
if (isShift) {
|
|
560
|
+
const meta = chart.getDatasetMeta(idx);
|
|
561
|
+
meta.hidden = !meta.hidden;
|
|
562
|
+
} else if (isCtrl) {
|
|
563
|
+
chart.getDatasetMeta(idx).hidden = false;
|
|
564
|
+
} else {
|
|
565
|
+
const allOthersHidden = datasets.every((_, i) => i === idx || !!chart.getDatasetMeta(i).hidden);
|
|
566
|
+
if (allOthersHidden) {
|
|
567
|
+
datasets.forEach((_, i) => { chart.getDatasetMeta(i).hidden = false; });
|
|
568
|
+
} else {
|
|
569
|
+
datasets.forEach((_, i) => { chart.getDatasetMeta(i).hidden = (i !== idx); });
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
}
|
|
573
|
+
chart.update();
|
|
574
|
+
}
|
|
575
|
+
|
|
511
576
|
// ── 图表实例 ──────────────────────────────────────────────
|
|
512
577
|
let chartTimeline = null;
|
|
513
578
|
let chartVendorDist = null;
|
|
@@ -529,28 +594,29 @@ async function fetchJSON(url) {
|
|
|
529
594
|
|
|
530
595
|
// ── KPI 更新 ──────────────────────────────────────────────
|
|
531
596
|
function updateKPI(summary) {
|
|
532
|
-
const t = summary.today,
|
|
597
|
+
const t = summary.today, r = summary.range;
|
|
598
|
+
const lbl = currentRangeLabel;
|
|
533
599
|
|
|
534
600
|
document.getElementById('kpi-req-today').textContent = fmtNum(t.requests);
|
|
535
|
-
document.getElementById('kpi-req-week').textContent = '
|
|
601
|
+
document.getElementById('kpi-req-week').textContent = lbl + ' ' + fmtNum(r.requests);
|
|
536
602
|
|
|
537
|
-
const tokT = t.tokens,
|
|
603
|
+
const tokT = t.tokens, tokR = r.tokens;
|
|
538
604
|
const totalT = tokT.input + tokT.output + tokT.cache_creation + tokT.cache_read;
|
|
539
|
-
const
|
|
605
|
+
const totalR = tokR.input + tokR.output + tokR.cache_creation + tokR.cache_read;
|
|
540
606
|
document.getElementById('kpi-tok-today').textContent = fmtTokens(totalT);
|
|
541
|
-
document.getElementById('kpi-tok-week').textContent = '
|
|
607
|
+
document.getElementById('kpi-tok-week').textContent = lbl + ' ' + fmtTokens(totalR);
|
|
542
608
|
|
|
543
609
|
document.getElementById('kpi-out-today').textContent = fmtTokens(tokT.output);
|
|
544
|
-
document.getElementById('kpi-out-week').textContent = '
|
|
610
|
+
document.getElementById('kpi-out-week').textContent = lbl + ' ' + fmtTokens(tokR.output);
|
|
545
611
|
|
|
546
612
|
document.getElementById('kpi-cost-today').textContent = t.cost || '–';
|
|
547
|
-
document.getElementById('kpi-cost-week').textContent = '
|
|
613
|
+
document.getElementById('kpi-cost-week').textContent = lbl + ' ' + (r.cost || '–');
|
|
548
614
|
|
|
549
615
|
document.getElementById('kpi-fo-today').textContent = fmtNum(t.failovers);
|
|
550
|
-
document.getElementById('kpi-fo-week').textContent = '
|
|
616
|
+
document.getElementById('kpi-fo-week').textContent = lbl + ' ' + fmtNum(r.failovers);
|
|
551
617
|
|
|
552
618
|
document.getElementById('kpi-lat-today').textContent = t.avg_duration_ms ? t.avg_duration_ms + 'ms' : '–';
|
|
553
|
-
document.getElementById('kpi-lat-week').textContent = '
|
|
619
|
+
document.getElementById('kpi-lat-week').textContent = lbl + ' ' + (r.avg_duration_ms ? r.avg_duration_ms + 'ms' : '–');
|
|
554
620
|
}
|
|
555
621
|
|
|
556
622
|
// ── 供应商状态 ────────────────────────────────────────────
|
|
@@ -578,6 +644,21 @@ function quotaBarColor(pct) {
|
|
|
578
644
|
if (pct >= 70) return 'var(--accent-yellow)';
|
|
579
645
|
return 'var(--accent-green)';
|
|
580
646
|
}
|
|
647
|
+
function quotaWindowLabel(wh) {
|
|
648
|
+
if (!wh) return '配额';
|
|
649
|
+
const h = parseFloat(wh);
|
|
650
|
+
if (h >= 24) return Math.round(h / 24) + 'd配额';
|
|
651
|
+
return Math.round(h) + 'h配额';
|
|
652
|
+
}
|
|
653
|
+
function renderQuotaBar(qg) {
|
|
654
|
+
if (!qg || qg.usage_percent == null) return '';
|
|
655
|
+
const pct = Math.round(qg.usage_percent);
|
|
656
|
+
const label = quotaWindowLabel(qg.window_hours);
|
|
657
|
+
return `<span class="status-badge ${quotaClass(pct)}">${label} ${pct}%</span>` +
|
|
658
|
+
`<div class="quota-bar-wrap"><div class="quota-bar-bg">` +
|
|
659
|
+
`<div class="quota-bar-fill" style="width:${Math.min(pct,100)}%;background:${quotaBarColor(pct)}"></div>` +
|
|
660
|
+
`</div></div>`;
|
|
661
|
+
}
|
|
581
662
|
|
|
582
663
|
function updateVendorStatus(status) {
|
|
583
664
|
const tiers = status.tiers || [];
|
|
@@ -588,25 +669,13 @@ function updateVendorStatus(status) {
|
|
|
588
669
|
}
|
|
589
670
|
list.innerHTML = tiers.map(tier => {
|
|
590
671
|
const cb = tier.circuit_breaker || {};
|
|
591
|
-
const qg = tier.quota_guard || {};
|
|
592
|
-
const wqg = tier.weekly_quota_guard || {};
|
|
593
672
|
const cbClass = cbStateClass(cb.state);
|
|
594
673
|
const cbLabel = cbStateLabel(cb.state);
|
|
595
|
-
const pct = qg.usage_percent != null ? Math.round(qg.usage_percent) : null;
|
|
596
|
-
const wpct = wqg.usage_percent != null ? Math.round(wqg.usage_percent) : null;
|
|
597
674
|
const initial = (tier.name || '?').charAt(0).toUpperCase();
|
|
598
675
|
|
|
599
676
|
let quotaHTML = '';
|
|
600
|
-
if (
|
|
601
|
-
|
|
602
|
-
<span class="status-badge ${quotaClass(pct)}">日配 ${pct}%</span>
|
|
603
|
-
<div class="quota-bar-wrap">
|
|
604
|
-
<div class="quota-bar-bg"><div class="quota-bar-fill" style="width:${Math.min(pct,100)}%;background:${quotaBarColor(pct)}"></div></div>
|
|
605
|
-
</div>`;
|
|
606
|
-
}
|
|
607
|
-
if (wpct != null) {
|
|
608
|
-
quotaHTML += `<span class="status-badge ${quotaClass(wpct)}">周配 ${wpct}%</span>`;
|
|
609
|
-
}
|
|
677
|
+
if (tier.quota_guard) quotaHTML += renderQuotaBar(tier.quota_guard);
|
|
678
|
+
if (tier.weekly_quota_guard) quotaHTML += renderQuotaBar(tier.weekly_quota_guard);
|
|
610
679
|
|
|
611
680
|
const rlInfo = tier.rate_limit || {};
|
|
612
681
|
const rlHtml = rlInfo.limited ? `<span class="status-badge sb-warn">限速中</span>` : '';
|
|
@@ -659,7 +728,10 @@ function buildTimeline(rows) {
|
|
|
659
728
|
options: {
|
|
660
729
|
responsive: true, maintainAspectRatio: false,
|
|
661
730
|
interaction: { mode: 'index', intersect: false },
|
|
662
|
-
plugins: {
|
|
731
|
+
plugins: {
|
|
732
|
+
legend: { ...COMMON_LEGEND, onClick: legendOnClick },
|
|
733
|
+
tooltip: { itemSort: (a, b) => (b.raw || 0) - (a.raw || 0) },
|
|
734
|
+
},
|
|
663
735
|
scales: {
|
|
664
736
|
x: COMMON_SCALE_X,
|
|
665
737
|
y: { ...COMMON_SCALE_Y, ticks: { precision: 0 } },
|
|
@@ -699,7 +771,7 @@ function buildVendorDist(rows) {
|
|
|
699
771
|
options: {
|
|
700
772
|
responsive: true, maintainAspectRatio: false,
|
|
701
773
|
plugins: {
|
|
702
|
-
legend: COMMON_LEGEND,
|
|
774
|
+
legend: { ...COMMON_LEGEND, onClick: legendOnClick },
|
|
703
775
|
tooltip: { callbacks: { label: c => ` ${c.label}: ${c.raw.toLocaleString()} 次` } },
|
|
704
776
|
},
|
|
705
777
|
},
|
|
@@ -748,8 +820,11 @@ function buildTokenTimeline(rows) {
|
|
|
748
820
|
responsive: true, maintainAspectRatio: false,
|
|
749
821
|
interaction: { mode: 'index', intersect: false },
|
|
750
822
|
plugins: {
|
|
751
|
-
legend: COMMON_LEGEND,
|
|
752
|
-
tooltip: {
|
|
823
|
+
legend: { ...COMMON_LEGEND, onClick: legendOnClick },
|
|
824
|
+
tooltip: {
|
|
825
|
+
itemSort: (a, b) => (b.raw || 0) - (a.raw || 0),
|
|
826
|
+
callbacks: { label: c => ` ${c.dataset.label}: ${fmtTokens(c.raw)}` },
|
|
827
|
+
},
|
|
753
828
|
},
|
|
754
829
|
scales: {
|
|
755
830
|
x: COMMON_SCALE_X,
|
|
@@ -811,16 +886,21 @@ function buildModelTokenTimeline(rows) {
|
|
|
811
886
|
plugins: {
|
|
812
887
|
legend: {
|
|
813
888
|
position: keys.length > 8 ? 'right' : 'bottom',
|
|
889
|
+
onClick: legendOnClick,
|
|
814
890
|
labels: {
|
|
815
891
|
...COMMON_LEGEND.labels,
|
|
816
|
-
generateLabels: chart =>
|
|
892
|
+
generateLabels: chart => {
|
|
893
|
+
const items = COMMON_LEGEND.labels.generateLabels(chart);
|
|
817
894
|
const maxLen = 32;
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
895
|
+
items.forEach(item => {
|
|
896
|
+
if (item.text.length > maxLen) item.text = item.text.slice(0, maxLen) + '…';
|
|
897
|
+
});
|
|
898
|
+
return items;
|
|
899
|
+
},
|
|
821
900
|
},
|
|
822
901
|
},
|
|
823
902
|
tooltip: {
|
|
903
|
+
itemSort: (a, b) => (b.raw || 0) - (a.raw || 0),
|
|
824
904
|
callbacks: {
|
|
825
905
|
label: c => ` ${c.dataset.label}: ${fmtTokens(c.raw)}`,
|
|
826
906
|
footer: items => {
|
|
@@ -842,26 +922,14 @@ function buildModelTokenTimeline(rows) {
|
|
|
842
922
|
});
|
|
843
923
|
}
|
|
844
924
|
|
|
845
|
-
// ── 故障转移明细表 ────────────────────────────────────────
|
|
846
|
-
function updateFtTable(failoverStats) {
|
|
847
|
-
const tbody = document.getElementById('ft-tbody');
|
|
848
|
-
if (!failoverStats || !failoverStats.length) {
|
|
849
|
-
tbody.innerHTML = '<tr><td colspan="3" class="empty"><div class="empty-icon">✅</div>暂无故障转移记录</td></tr>';
|
|
850
|
-
return;
|
|
851
|
-
}
|
|
852
|
-
tbody.innerHTML = failoverStats.map(r => `
|
|
853
|
-
<tr>
|
|
854
|
-
<td><span class="tag-vendor">${r.failover_from || 'unknown'}</span></td>
|
|
855
|
-
<td><span class="tag-vendor">${r.vendor || ''}</span></td>
|
|
856
|
-
<td><span style="font-family:'JetBrains Mono',monospace">${fmtNum(r.count)}</span></td>
|
|
857
|
-
</tr>`).join('');
|
|
858
|
-
}
|
|
859
|
-
|
|
860
925
|
// ── 时间区间控制 ──────────────────────────────────────────
|
|
861
926
|
let currentDays = 7;
|
|
927
|
+
let currentRangeLabel = '本周';
|
|
862
928
|
|
|
863
929
|
function setTimeRange(days, btn) {
|
|
864
930
|
currentDays = days;
|
|
931
|
+
if (days === 7) currentRangeLabel = '本周';
|
|
932
|
+
else if (days === 30) currentRangeLabel = '本月';
|
|
865
933
|
document.querySelectorAll('.range-btn').forEach(b => b.classList.remove('active'));
|
|
866
934
|
if (btn) btn.classList.add('active');
|
|
867
935
|
const customEl = document.getElementById('range-custom');
|
|
@@ -887,6 +955,7 @@ function applyCustomRange() {
|
|
|
887
955
|
const endMs = new Date(e).getTime();
|
|
888
956
|
if (endMs < startMs) return;
|
|
889
957
|
currentDays = Math.ceil((endMs - startMs) / 86400000) + 1;
|
|
958
|
+
currentRangeLabel = s + '—' + e;
|
|
890
959
|
refresh();
|
|
891
960
|
}
|
|
892
961
|
|
|
@@ -911,7 +980,7 @@ async function refresh() {
|
|
|
911
980
|
try {
|
|
912
981
|
const days = currentDays > 0 ? currentDays : 7;
|
|
913
982
|
const [summary, timeline, status] = await Promise.all([
|
|
914
|
-
fetchJSON('/api/dashboard/summary'),
|
|
983
|
+
fetchJSON('/api/dashboard/summary?days=' + days),
|
|
915
984
|
fetchJSON('/api/dashboard/timeline?days=' + days),
|
|
916
985
|
fetchJSON('/api/status'),
|
|
917
986
|
]);
|
|
@@ -930,8 +999,6 @@ async function refresh() {
|
|
|
930
999
|
buildTokenTimeline(rows);
|
|
931
1000
|
buildModelTokenTimeline(rows);
|
|
932
1001
|
|
|
933
|
-
updateFtTable(summary.failover_stats || []);
|
|
934
|
-
|
|
935
1002
|
document.getElementById('refresh-time').textContent = '上次刷新: ' + now();
|
|
936
1003
|
} catch (e) {
|
|
937
1004
|
console.error('Dashboard refresh error:', e);
|
|
@@ -943,7 +1010,7 @@ async function refresh() {
|
|
|
943
1010
|
|
|
944
1011
|
// 页面加载 + 每 30 秒自动刷新
|
|
945
1012
|
refresh();
|
|
946
|
-
setInterval(refresh,
|
|
1013
|
+
setInterval(refresh, 600000);
|
|
947
1014
|
</script>
|
|
948
1015
|
</body>
|
|
949
1016
|
</html>
|
|
@@ -1030,8 +1097,8 @@ def register_dashboard_routes(app: Any) -> None:
|
|
|
1030
1097
|
return HTMLResponse(content=_DASHBOARD_HTML)
|
|
1031
1098
|
|
|
1032
1099
|
@app.get("/api/dashboard/summary")
|
|
1033
|
-
async def dashboard_summary(request: Request) -> Response:
|
|
1034
|
-
"""返回 Dashboard 汇总数据(今日 /
|
|
1100
|
+
async def dashboard_summary(request: Request, days: int = 7) -> Response:
|
|
1101
|
+
"""返回 Dashboard 汇总数据(今日 / 所选区间)."""
|
|
1035
1102
|
token_logger = getattr(request.app.state, "token_logger", None)
|
|
1036
1103
|
pricing_table = getattr(request.app.state, "pricing_table", None)
|
|
1037
1104
|
|
|
@@ -1042,15 +1109,17 @@ def register_dashboard_routes(app: Any) -> None:
|
|
|
1042
1109
|
media_type="application/json",
|
|
1043
1110
|
)
|
|
1044
1111
|
|
|
1112
|
+
days = max(1, min(days, 90)) # 限制范围 1~90 天
|
|
1113
|
+
|
|
1045
1114
|
try:
|
|
1046
1115
|
# 今日(最近 1 天)
|
|
1047
1116
|
today_rows = await token_logger.query_usage(period=TimePeriod.DAY, count=1)
|
|
1048
|
-
#
|
|
1049
|
-
|
|
1050
|
-
|
|
1051
|
-
|
|
1052
|
-
#
|
|
1053
|
-
failover_stats = await token_logger.query_failover_stats(days=
|
|
1117
|
+
# 所选区间
|
|
1118
|
+
range_rows = await token_logger.query_usage(
|
|
1119
|
+
period=TimePeriod.DAY, count=days
|
|
1120
|
+
)
|
|
1121
|
+
# 故障转移(所选区间)
|
|
1122
|
+
failover_stats = await token_logger.query_failover_stats(days=days)
|
|
1054
1123
|
except Exception as exc:
|
|
1055
1124
|
logger.error("dashboard_summary query error: %s", exc, exc_info=True)
|
|
1056
1125
|
return Response(
|
|
@@ -1060,18 +1129,15 @@ def register_dashboard_routes(app: Any) -> None:
|
|
|
1060
1129
|
)
|
|
1061
1130
|
|
|
1062
1131
|
today = _sum_rows(today_rows)
|
|
1063
|
-
|
|
1064
|
-
month = _sum_rows(month_rows)
|
|
1132
|
+
range_stat = _sum_rows(range_rows)
|
|
1065
1133
|
|
|
1066
1134
|
today["cost"] = _compute_cost_str(today_rows, pricing_table)
|
|
1067
|
-
|
|
1068
|
-
month["cost"] = _compute_cost_str(month_rows, pricing_table)
|
|
1135
|
+
range_stat["cost"] = _compute_cost_str(range_rows, pricing_table)
|
|
1069
1136
|
|
|
1070
1137
|
result = {
|
|
1071
1138
|
"version": __version__,
|
|
1072
1139
|
"today": today,
|
|
1073
|
-
"
|
|
1074
|
-
"month": month,
|
|
1140
|
+
"range": range_stat,
|
|
1075
1141
|
"failover_stats": failover_stats,
|
|
1076
1142
|
}
|
|
1077
1143
|
return Response(
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/convert/anthropic_to_gemini.py
RENAMED
|
File without changes
|
{coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/convert/anthropic_to_openai.py
RENAMED
|
File without changes
|
{coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/convert/gemini_sse_adapter.py
RENAMED
|
File without changes
|
{coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/convert/gemini_to_anthropic.py
RENAMED
|
File without changes
|
{coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/convert/openai_to_anthropic.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/streaming/anthropic_compat.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coding_proxy-0.2.3a3 → coding_proxy-0.2.3a5}/src/coding/proxy/vendors/copilot_token_manager.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|