coding-proxy 0.2.3a3__tar.gz → 0.2.3a4__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.3a4}/PKG-INFO +1 -1
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/pyproject.toml +1 -1
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/config/config.default.yaml +1 -1
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/logging/db.py +1 -1
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/routing/executor.py +6 -1
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/routing/quota_guard.py +1 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/server/dashboard.py +159 -94
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/uv.lock +1 -1
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/.github/workflows/ci.yml +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/.github/workflows/coverage.yml +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/.github/workflows/release.yml +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/.gitignore +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/AGENTS.md +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/CHANGELOG.md +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/CLAUDE.md +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/LICENSE +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/README.md +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/docs/ci-cd.md +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/docs/framework.md +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/docs/user-guide.md +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/docs/zh-CN/README.md +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/__main__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/auth/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/auth/providers/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/auth/providers/base.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/auth/providers/github.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/auth/providers/google.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/auth/runtime.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/auth/store.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/cli/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/cli/auth_commands.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/cli/banner.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/compat/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/compat/canonical.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/compat/session_store.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/config/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/config/auth_schema.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/config/loader.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/config/resiliency.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/config/routing.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/config/schema.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/config/server.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/config/vendors.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/convert/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/convert/anthropic_to_gemini.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/convert/anthropic_to_openai.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/convert/gemini_sse_adapter.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/convert/gemini_to_anthropic.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/convert/openai_to_anthropic.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/logging/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/logging/formatters.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/logging/stats.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/model/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/model/auth.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/model/compat.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/model/constants.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/model/pricing.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/model/token.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/model/vendor.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/pricing.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/routing/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/routing/circuit_breaker.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/routing/error_classifier.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/routing/model_mapper.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/routing/rate_limit.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/routing/retry.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/routing/router.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/routing/session_manager.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/routing/tier.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/routing/usage_parser.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/routing/usage_recorder.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/server/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/server/app.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/server/factory.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/server/request_normalizer.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/server/responses.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/server/routes.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/streaming/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/streaming/anthropic_compat.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/vendors/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/vendors/alibaba.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/vendors/anthropic.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/vendors/antigravity.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/vendors/base.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/vendors/copilot.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/vendors/copilot_models.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/vendors/copilot_token_manager.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/vendors/copilot_urls.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/vendors/doubao.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/vendors/kimi.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/vendors/minimax.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/vendors/mixins.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/vendors/native_anthropic.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/vendors/token_manager.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/vendors/xiaomi.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/vendors/zhipu.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/__init__.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_antigravity.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_app_routes.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_auto_login.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_banner.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_circuit_breaker.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_cli_usage.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_compat.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_config_init.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_config_loader.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_convert_request.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_convert_response.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_convert_sse.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_copilot.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_copilot_convert_request.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_copilot_convert_response.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_copilot_models.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_copilot_urls.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_currency.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_error_classifier.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_logging_dual_write.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_mixins.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_model_auth.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_model_compat.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_model_constants.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_model_mapper.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_model_pricing.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_model_token.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_model_vendor.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_native_vendors.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_parse_usage.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_pricing.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_quota_guard.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_rate_limit.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_request_normalizer.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_router_chain.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_router_executor.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_runtime_reauth.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_schema.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_streaming_anthropic_compat.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_tier.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_tiers_config.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_time_range.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_token_logger.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_token_manager.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_types.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_vendor_streaming.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/tests/test_vendors.py +0 -0
- {coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/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.3a4
|
|
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.3a4"
|
|
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
|
// ── 工具函数 ──────────────────────────────────────────────
|
|
@@ -505,9 +510,68 @@ Chart.defaults.font.size = 11;
|
|
|
505
510
|
|
|
506
511
|
const COMMON_SCALE_X = { grid: { display: false }, ticks: { maxTicksLimit: 10 } };
|
|
507
512
|
const COMMON_SCALE_Y = { grid: { color: 'rgba(255,255,255,.04)' }, beginAtZero: true };
|
|
508
|
-
const COMMON_LEGEND = {
|
|
513
|
+
const COMMON_LEGEND = {
|
|
514
|
+
position: 'bottom',
|
|
515
|
+
labels: {
|
|
516
|
+
boxWidth: 8,
|
|
517
|
+
padding: 14,
|
|
518
|
+
usePointStyle: true,
|
|
519
|
+
pointStyle: 'circle',
|
|
520
|
+
pointStyleWidth: 8,
|
|
521
|
+
font: { size: 11 },
|
|
522
|
+
generateLabels: chart => {
|
|
523
|
+
const items = Chart.defaults.plugins.legend.labels.generateLabels(chart);
|
|
524
|
+
items.forEach(item => { item.pointStyle = 'circle'; item.lineWidth = 0; });
|
|
525
|
+
return items;
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
};
|
|
509
529
|
const COMMON_LINE_DATASET = { tension: .35, pointRadius: 0, pointHoverRadius: 5, borderWidth: 2 };
|
|
510
530
|
|
|
531
|
+
// ── Legend 点击交互:单击=仅选该项,Ctrl/Meta+单击=多选追加,Shift+单击=排除 ──
|
|
532
|
+
function legendOnClick(e, legendItem, legend) {
|
|
533
|
+
const chart = legend.chart;
|
|
534
|
+
const isShift = e.native.shiftKey;
|
|
535
|
+
const isCtrl = e.native.ctrlKey || e.native.metaKey;
|
|
536
|
+
if (chart.config.type === 'doughnut' || chart.config.type === 'pie') {
|
|
537
|
+
const idx = legendItem.index;
|
|
538
|
+
const dataLen = chart.data.labels.length;
|
|
539
|
+
if (isShift) {
|
|
540
|
+
chart.toggleDataVisibility(idx);
|
|
541
|
+
} else if (isCtrl) {
|
|
542
|
+
if (!chart.getDataVisibility(idx)) chart.toggleDataVisibility(idx);
|
|
543
|
+
} else {
|
|
544
|
+
const allOthersHidden = [...Array(dataLen).keys()].filter(i => i !== idx).every(i => !chart.getDataVisibility(i));
|
|
545
|
+
if (allOthersHidden) {
|
|
546
|
+
for (let i = 0; i < dataLen; i++) { if (!chart.getDataVisibility(i)) chart.toggleDataVisibility(i); }
|
|
547
|
+
} else {
|
|
548
|
+
for (let i = 0; i < dataLen; i++) {
|
|
549
|
+
const vis = chart.getDataVisibility(i);
|
|
550
|
+
if (i === idx && !vis) chart.toggleDataVisibility(i);
|
|
551
|
+
if (i !== idx && vis) chart.toggleDataVisibility(i);
|
|
552
|
+
}
|
|
553
|
+
}
|
|
554
|
+
}
|
|
555
|
+
} else {
|
|
556
|
+
const idx = legendItem.datasetIndex;
|
|
557
|
+
const datasets = chart.data.datasets;
|
|
558
|
+
if (isShift) {
|
|
559
|
+
const meta = chart.getDatasetMeta(idx);
|
|
560
|
+
meta.hidden = !meta.hidden;
|
|
561
|
+
} else if (isCtrl) {
|
|
562
|
+
chart.getDatasetMeta(idx).hidden = false;
|
|
563
|
+
} else {
|
|
564
|
+
const allOthersHidden = datasets.every((_, i) => i === idx || !!chart.getDatasetMeta(i).hidden);
|
|
565
|
+
if (allOthersHidden) {
|
|
566
|
+
datasets.forEach((_, i) => { chart.getDatasetMeta(i).hidden = false; });
|
|
567
|
+
} else {
|
|
568
|
+
datasets.forEach((_, i) => { chart.getDatasetMeta(i).hidden = (i !== idx); });
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
chart.update();
|
|
573
|
+
}
|
|
574
|
+
|
|
511
575
|
// ── 图表实例 ──────────────────────────────────────────────
|
|
512
576
|
let chartTimeline = null;
|
|
513
577
|
let chartVendorDist = null;
|
|
@@ -529,28 +593,29 @@ async function fetchJSON(url) {
|
|
|
529
593
|
|
|
530
594
|
// ── KPI 更新 ──────────────────────────────────────────────
|
|
531
595
|
function updateKPI(summary) {
|
|
532
|
-
const t = summary.today,
|
|
596
|
+
const t = summary.today, r = summary.range;
|
|
597
|
+
const lbl = currentRangeLabel;
|
|
533
598
|
|
|
534
599
|
document.getElementById('kpi-req-today').textContent = fmtNum(t.requests);
|
|
535
|
-
document.getElementById('kpi-req-week').textContent = '
|
|
600
|
+
document.getElementById('kpi-req-week').textContent = lbl + ' ' + fmtNum(r.requests);
|
|
536
601
|
|
|
537
|
-
const tokT = t.tokens,
|
|
602
|
+
const tokT = t.tokens, tokR = r.tokens;
|
|
538
603
|
const totalT = tokT.input + tokT.output + tokT.cache_creation + tokT.cache_read;
|
|
539
|
-
const
|
|
604
|
+
const totalR = tokR.input + tokR.output + tokR.cache_creation + tokR.cache_read;
|
|
540
605
|
document.getElementById('kpi-tok-today').textContent = fmtTokens(totalT);
|
|
541
|
-
document.getElementById('kpi-tok-week').textContent = '
|
|
606
|
+
document.getElementById('kpi-tok-week').textContent = lbl + ' ' + fmtTokens(totalR);
|
|
542
607
|
|
|
543
608
|
document.getElementById('kpi-out-today').textContent = fmtTokens(tokT.output);
|
|
544
|
-
document.getElementById('kpi-out-week').textContent = '
|
|
609
|
+
document.getElementById('kpi-out-week').textContent = lbl + ' ' + fmtTokens(tokR.output);
|
|
545
610
|
|
|
546
611
|
document.getElementById('kpi-cost-today').textContent = t.cost || '–';
|
|
547
|
-
document.getElementById('kpi-cost-week').textContent = '
|
|
612
|
+
document.getElementById('kpi-cost-week').textContent = lbl + ' ' + (r.cost || '–');
|
|
548
613
|
|
|
549
614
|
document.getElementById('kpi-fo-today').textContent = fmtNum(t.failovers);
|
|
550
|
-
document.getElementById('kpi-fo-week').textContent = '
|
|
615
|
+
document.getElementById('kpi-fo-week').textContent = lbl + ' ' + fmtNum(r.failovers);
|
|
551
616
|
|
|
552
617
|
document.getElementById('kpi-lat-today').textContent = t.avg_duration_ms ? t.avg_duration_ms + 'ms' : '–';
|
|
553
|
-
document.getElementById('kpi-lat-week').textContent = '
|
|
618
|
+
document.getElementById('kpi-lat-week').textContent = lbl + ' ' + (r.avg_duration_ms ? r.avg_duration_ms + 'ms' : '–');
|
|
554
619
|
}
|
|
555
620
|
|
|
556
621
|
// ── 供应商状态 ────────────────────────────────────────────
|
|
@@ -578,6 +643,21 @@ function quotaBarColor(pct) {
|
|
|
578
643
|
if (pct >= 70) return 'var(--accent-yellow)';
|
|
579
644
|
return 'var(--accent-green)';
|
|
580
645
|
}
|
|
646
|
+
function quotaWindowLabel(wh) {
|
|
647
|
+
if (!wh) return '配额';
|
|
648
|
+
const h = parseFloat(wh);
|
|
649
|
+
if (h >= 24) return Math.round(h / 24) + 'd配额';
|
|
650
|
+
return Math.round(h) + 'h配额';
|
|
651
|
+
}
|
|
652
|
+
function renderQuotaBar(qg) {
|
|
653
|
+
if (!qg || qg.usage_percent == null) return '';
|
|
654
|
+
const pct = Math.round(qg.usage_percent);
|
|
655
|
+
const label = quotaWindowLabel(qg.window_hours);
|
|
656
|
+
return `<span class="status-badge ${quotaClass(pct)}">${label} ${pct}%</span>` +
|
|
657
|
+
`<div class="quota-bar-wrap"><div class="quota-bar-bg">` +
|
|
658
|
+
`<div class="quota-bar-fill" style="width:${Math.min(pct,100)}%;background:${quotaBarColor(pct)}"></div>` +
|
|
659
|
+
`</div></div>`;
|
|
660
|
+
}
|
|
581
661
|
|
|
582
662
|
function updateVendorStatus(status) {
|
|
583
663
|
const tiers = status.tiers || [];
|
|
@@ -588,25 +668,13 @@ function updateVendorStatus(status) {
|
|
|
588
668
|
}
|
|
589
669
|
list.innerHTML = tiers.map(tier => {
|
|
590
670
|
const cb = tier.circuit_breaker || {};
|
|
591
|
-
const qg = tier.quota_guard || {};
|
|
592
|
-
const wqg = tier.weekly_quota_guard || {};
|
|
593
671
|
const cbClass = cbStateClass(cb.state);
|
|
594
672
|
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
673
|
const initial = (tier.name || '?').charAt(0).toUpperCase();
|
|
598
674
|
|
|
599
675
|
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
|
-
}
|
|
676
|
+
if (tier.quota_guard) quotaHTML += renderQuotaBar(tier.quota_guard);
|
|
677
|
+
if (tier.weekly_quota_guard) quotaHTML += renderQuotaBar(tier.weekly_quota_guard);
|
|
610
678
|
|
|
611
679
|
const rlInfo = tier.rate_limit || {};
|
|
612
680
|
const rlHtml = rlInfo.limited ? `<span class="status-badge sb-warn">限速中</span>` : '';
|
|
@@ -659,7 +727,10 @@ function buildTimeline(rows) {
|
|
|
659
727
|
options: {
|
|
660
728
|
responsive: true, maintainAspectRatio: false,
|
|
661
729
|
interaction: { mode: 'index', intersect: false },
|
|
662
|
-
plugins: {
|
|
730
|
+
plugins: {
|
|
731
|
+
legend: { ...COMMON_LEGEND, onClick: legendOnClick },
|
|
732
|
+
tooltip: { itemSort: (a, b) => (b.raw || 0) - (a.raw || 0) },
|
|
733
|
+
},
|
|
663
734
|
scales: {
|
|
664
735
|
x: COMMON_SCALE_X,
|
|
665
736
|
y: { ...COMMON_SCALE_Y, ticks: { precision: 0 } },
|
|
@@ -699,7 +770,7 @@ function buildVendorDist(rows) {
|
|
|
699
770
|
options: {
|
|
700
771
|
responsive: true, maintainAspectRatio: false,
|
|
701
772
|
plugins: {
|
|
702
|
-
legend: COMMON_LEGEND,
|
|
773
|
+
legend: { ...COMMON_LEGEND, onClick: legendOnClick },
|
|
703
774
|
tooltip: { callbacks: { label: c => ` ${c.label}: ${c.raw.toLocaleString()} 次` } },
|
|
704
775
|
},
|
|
705
776
|
},
|
|
@@ -748,8 +819,11 @@ function buildTokenTimeline(rows) {
|
|
|
748
819
|
responsive: true, maintainAspectRatio: false,
|
|
749
820
|
interaction: { mode: 'index', intersect: false },
|
|
750
821
|
plugins: {
|
|
751
|
-
legend: COMMON_LEGEND,
|
|
752
|
-
tooltip: {
|
|
822
|
+
legend: { ...COMMON_LEGEND, onClick: legendOnClick },
|
|
823
|
+
tooltip: {
|
|
824
|
+
itemSort: (a, b) => (b.raw || 0) - (a.raw || 0),
|
|
825
|
+
callbacks: { label: c => ` ${c.dataset.label}: ${fmtTokens(c.raw)}` },
|
|
826
|
+
},
|
|
753
827
|
},
|
|
754
828
|
scales: {
|
|
755
829
|
x: COMMON_SCALE_X,
|
|
@@ -811,16 +885,21 @@ function buildModelTokenTimeline(rows) {
|
|
|
811
885
|
plugins: {
|
|
812
886
|
legend: {
|
|
813
887
|
position: keys.length > 8 ? 'right' : 'bottom',
|
|
888
|
+
onClick: legendOnClick,
|
|
814
889
|
labels: {
|
|
815
890
|
...COMMON_LEGEND.labels,
|
|
816
|
-
generateLabels: chart =>
|
|
891
|
+
generateLabels: chart => {
|
|
892
|
+
const items = COMMON_LEGEND.labels.generateLabels(chart);
|
|
817
893
|
const maxLen = 32;
|
|
818
|
-
|
|
819
|
-
|
|
820
|
-
|
|
894
|
+
items.forEach(item => {
|
|
895
|
+
if (item.text.length > maxLen) item.text = item.text.slice(0, maxLen) + '…';
|
|
896
|
+
});
|
|
897
|
+
return items;
|
|
898
|
+
},
|
|
821
899
|
},
|
|
822
900
|
},
|
|
823
901
|
tooltip: {
|
|
902
|
+
itemSort: (a, b) => (b.raw || 0) - (a.raw || 0),
|
|
824
903
|
callbacks: {
|
|
825
904
|
label: c => ` ${c.dataset.label}: ${fmtTokens(c.raw)}`,
|
|
826
905
|
footer: items => {
|
|
@@ -842,26 +921,14 @@ function buildModelTokenTimeline(rows) {
|
|
|
842
921
|
});
|
|
843
922
|
}
|
|
844
923
|
|
|
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
924
|
// ── 时间区间控制 ──────────────────────────────────────────
|
|
861
925
|
let currentDays = 7;
|
|
926
|
+
let currentRangeLabel = '本周';
|
|
862
927
|
|
|
863
928
|
function setTimeRange(days, btn) {
|
|
864
929
|
currentDays = days;
|
|
930
|
+
if (days === 7) currentRangeLabel = '本周';
|
|
931
|
+
else if (days === 30) currentRangeLabel = '本月';
|
|
865
932
|
document.querySelectorAll('.range-btn').forEach(b => b.classList.remove('active'));
|
|
866
933
|
if (btn) btn.classList.add('active');
|
|
867
934
|
const customEl = document.getElementById('range-custom');
|
|
@@ -887,6 +954,7 @@ function applyCustomRange() {
|
|
|
887
954
|
const endMs = new Date(e).getTime();
|
|
888
955
|
if (endMs < startMs) return;
|
|
889
956
|
currentDays = Math.ceil((endMs - startMs) / 86400000) + 1;
|
|
957
|
+
currentRangeLabel = s + '—' + e;
|
|
890
958
|
refresh();
|
|
891
959
|
}
|
|
892
960
|
|
|
@@ -911,7 +979,7 @@ async function refresh() {
|
|
|
911
979
|
try {
|
|
912
980
|
const days = currentDays > 0 ? currentDays : 7;
|
|
913
981
|
const [summary, timeline, status] = await Promise.all([
|
|
914
|
-
fetchJSON('/api/dashboard/summary'),
|
|
982
|
+
fetchJSON('/api/dashboard/summary?days=' + days),
|
|
915
983
|
fetchJSON('/api/dashboard/timeline?days=' + days),
|
|
916
984
|
fetchJSON('/api/status'),
|
|
917
985
|
]);
|
|
@@ -930,8 +998,6 @@ async function refresh() {
|
|
|
930
998
|
buildTokenTimeline(rows);
|
|
931
999
|
buildModelTokenTimeline(rows);
|
|
932
1000
|
|
|
933
|
-
updateFtTable(summary.failover_stats || []);
|
|
934
|
-
|
|
935
1001
|
document.getElementById('refresh-time').textContent = '上次刷新: ' + now();
|
|
936
1002
|
} catch (e) {
|
|
937
1003
|
console.error('Dashboard refresh error:', e);
|
|
@@ -1030,8 +1096,8 @@ def register_dashboard_routes(app: Any) -> None:
|
|
|
1030
1096
|
return HTMLResponse(content=_DASHBOARD_HTML)
|
|
1031
1097
|
|
|
1032
1098
|
@app.get("/api/dashboard/summary")
|
|
1033
|
-
async def dashboard_summary(request: Request) -> Response:
|
|
1034
|
-
"""返回 Dashboard 汇总数据(今日 /
|
|
1099
|
+
async def dashboard_summary(request: Request, days: int = 7) -> Response:
|
|
1100
|
+
"""返回 Dashboard 汇总数据(今日 / 所选区间)."""
|
|
1035
1101
|
token_logger = getattr(request.app.state, "token_logger", None)
|
|
1036
1102
|
pricing_table = getattr(request.app.state, "pricing_table", None)
|
|
1037
1103
|
|
|
@@ -1042,15 +1108,17 @@ def register_dashboard_routes(app: Any) -> None:
|
|
|
1042
1108
|
media_type="application/json",
|
|
1043
1109
|
)
|
|
1044
1110
|
|
|
1111
|
+
days = max(1, min(days, 90)) # 限制范围 1~90 天
|
|
1112
|
+
|
|
1045
1113
|
try:
|
|
1046
1114
|
# 今日(最近 1 天)
|
|
1047
1115
|
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=
|
|
1116
|
+
# 所选区间
|
|
1117
|
+
range_rows = await token_logger.query_usage(
|
|
1118
|
+
period=TimePeriod.DAY, count=days
|
|
1119
|
+
)
|
|
1120
|
+
# 故障转移(所选区间)
|
|
1121
|
+
failover_stats = await token_logger.query_failover_stats(days=days)
|
|
1054
1122
|
except Exception as exc:
|
|
1055
1123
|
logger.error("dashboard_summary query error: %s", exc, exc_info=True)
|
|
1056
1124
|
return Response(
|
|
@@ -1060,18 +1128,15 @@ def register_dashboard_routes(app: Any) -> None:
|
|
|
1060
1128
|
)
|
|
1061
1129
|
|
|
1062
1130
|
today = _sum_rows(today_rows)
|
|
1063
|
-
|
|
1064
|
-
month = _sum_rows(month_rows)
|
|
1131
|
+
range_stat = _sum_rows(range_rows)
|
|
1065
1132
|
|
|
1066
1133
|
today["cost"] = _compute_cost_str(today_rows, pricing_table)
|
|
1067
|
-
|
|
1068
|
-
month["cost"] = _compute_cost_str(month_rows, pricing_table)
|
|
1134
|
+
range_stat["cost"] = _compute_cost_str(range_rows, pricing_table)
|
|
1069
1135
|
|
|
1070
1136
|
result = {
|
|
1071
1137
|
"version": __version__,
|
|
1072
1138
|
"today": today,
|
|
1073
|
-
"
|
|
1074
|
-
"month": month,
|
|
1139
|
+
"range": range_stat,
|
|
1075
1140
|
"failover_stats": failover_stats,
|
|
1076
1141
|
}
|
|
1077
1142
|
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.3a4}/src/coding/proxy/convert/anthropic_to_gemini.py
RENAMED
|
File without changes
|
{coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/convert/anthropic_to_openai.py
RENAMED
|
File without changes
|
{coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/convert/gemini_sse_adapter.py
RENAMED
|
File without changes
|
{coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/src/coding/proxy/convert/gemini_to_anthropic.py
RENAMED
|
File without changes
|
{coding_proxy-0.2.3a3 → coding_proxy-0.2.3a4}/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.3a4}/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.3a4}/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
|