coding-proxy 0.2.3a1__tar.gz → 0.2.3a2__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.3a1 → coding_proxy-0.2.3a2}/PKG-INFO +1 -1
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/pyproject.toml +1 -1
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/server/dashboard.py +176 -39
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/uv.lock +1 -1
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/.github/workflows/ci.yml +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/.github/workflows/coverage.yml +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/.github/workflows/release.yml +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/.gitignore +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/AGENTS.md +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/CHANGELOG.md +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/CLAUDE.md +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/LICENSE +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/README.md +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/docs/ci-cd.md +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/docs/framework.md +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/docs/user-guide.md +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/docs/zh-CN/README.md +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/__init__.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/__init__.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/__main__.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/auth/__init__.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/auth/providers/__init__.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/auth/providers/base.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/auth/providers/github.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/auth/providers/google.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/auth/runtime.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/auth/store.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/cli/__init__.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/cli/auth_commands.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/cli/banner.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/compat/__init__.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/compat/canonical.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/compat/session_store.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/config/__init__.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/config/auth_schema.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/config/config.default.yaml +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/config/loader.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/config/resiliency.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/config/routing.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/config/schema.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/config/server.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/config/vendors.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/convert/__init__.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/convert/anthropic_to_gemini.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/convert/anthropic_to_openai.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/convert/gemini_sse_adapter.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/convert/gemini_to_anthropic.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/convert/openai_to_anthropic.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/logging/__init__.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/logging/db.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/logging/formatters.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/logging/stats.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/model/__init__.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/model/auth.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/model/compat.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/model/constants.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/model/pricing.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/model/token.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/model/vendor.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/pricing.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/routing/__init__.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/routing/circuit_breaker.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/routing/error_classifier.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/routing/executor.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/routing/model_mapper.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/routing/quota_guard.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/routing/rate_limit.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/routing/retry.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/routing/router.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/routing/session_manager.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/routing/tier.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/routing/usage_parser.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/routing/usage_recorder.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/server/__init__.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/server/app.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/server/factory.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/server/request_normalizer.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/server/responses.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/server/routes.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/streaming/__init__.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/streaming/anthropic_compat.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/vendors/__init__.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/vendors/alibaba.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/vendors/anthropic.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/vendors/antigravity.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/vendors/base.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/vendors/copilot.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/vendors/copilot_models.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/vendors/copilot_token_manager.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/vendors/copilot_urls.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/vendors/doubao.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/vendors/kimi.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/vendors/minimax.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/vendors/mixins.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/vendors/native_anthropic.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/vendors/token_manager.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/vendors/xiaomi.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/vendors/zhipu.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/__init__.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_antigravity.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_app_routes.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_auto_login.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_banner.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_circuit_breaker.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_cli_usage.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_compat.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_config_init.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_config_loader.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_convert_request.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_convert_response.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_convert_sse.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_copilot.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_copilot_convert_request.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_copilot_convert_response.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_copilot_models.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_copilot_urls.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_currency.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_error_classifier.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_logging_dual_write.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_mixins.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_model_auth.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_model_compat.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_model_constants.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_model_mapper.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_model_pricing.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_model_token.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_model_vendor.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_native_vendors.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_parse_usage.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_pricing.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_quota_guard.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_rate_limit.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_request_normalizer.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_router_chain.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_router_executor.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_runtime_reauth.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_schema.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_streaming_anthropic_compat.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_tier.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_tiers_config.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_time_range.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_token_logger.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_token_manager.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_types.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_vendor_streaming.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/tests/test_vendors.py +0 -0
- {coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/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.3a2
|
|
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.3a2"
|
|
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"
|
|
@@ -11,6 +11,40 @@ from fastapi.responses import HTMLResponse, Response
|
|
|
11
11
|
|
|
12
12
|
from ..logging.db import TimePeriod
|
|
13
13
|
|
|
14
|
+
|
|
15
|
+
# ── Favicon (16×16, 蓝紫渐变) ────────────────────────────────────────────
|
|
16
|
+
def _build_favicon() -> bytes:
|
|
17
|
+
"""程序化生成 16×16 ICO,蓝紫渐变与 Dashboard Logo 一致."""
|
|
18
|
+
import struct
|
|
19
|
+
|
|
20
|
+
width, height = 16, 16
|
|
21
|
+
pixel_rows: list[bytes] = []
|
|
22
|
+
for y in range(height - 1, -1, -1): # BMP bottom-up
|
|
23
|
+
row = bytearray()
|
|
24
|
+
for x in range(width):
|
|
25
|
+
t = (x + (height - 1 - y)) / (width + height - 2)
|
|
26
|
+
r = int(88 + (188 - 88) * t)
|
|
27
|
+
g = int(166 + (140 - 166) * t)
|
|
28
|
+
b = 255
|
|
29
|
+
row.extend([b, g, r, 255]) # BGRA
|
|
30
|
+
pixel_rows.append(bytes(row))
|
|
31
|
+
|
|
32
|
+
bmp_hdr = struct.pack(
|
|
33
|
+
"<IIIHHIIIIII", 40, width, height * 2, 1, 32, 0, 0, 0, 0, 0, 0
|
|
34
|
+
)
|
|
35
|
+
px_data = b"".join(pixel_rows)
|
|
36
|
+
mask_data = b"\x00\x00\x00\x00" * height
|
|
37
|
+
image_data = bmp_hdr + px_data + mask_data
|
|
38
|
+
|
|
39
|
+
ico_hdr = struct.pack("<HHH", 0, 1, 1)
|
|
40
|
+
dir_entry = struct.pack(
|
|
41
|
+
"<BBBBHHII", width, height, 0, 0, 1, 32, len(image_data), 22
|
|
42
|
+
)
|
|
43
|
+
return ico_hdr + dir_entry + image_data
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
_FAVICON_ICO: bytes = _build_favicon()
|
|
47
|
+
|
|
14
48
|
logger = logging.getLogger(__name__)
|
|
15
49
|
|
|
16
50
|
# ── HTML 模板 ──────────────────────────────────────────────────────────────
|
|
@@ -20,7 +54,8 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
20
54
|
<head>
|
|
21
55
|
<meta charset="UTF-8" />
|
|
22
56
|
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
23
|
-
<title>
|
|
57
|
+
<title>Coding Proxy Dashboard</title>
|
|
58
|
+
<link rel="icon" type="image/x-icon" href="/favicon.ico" />
|
|
24
59
|
<script src="https://cdn.jsdelivr.net/npm/chart.js@4.4.3/dist/chart.umd.min.js"></script>
|
|
25
60
|
<style>
|
|
26
61
|
:root {
|
|
@@ -125,7 +160,7 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
125
160
|
}
|
|
126
161
|
.charts-grid-3 {
|
|
127
162
|
display: grid;
|
|
128
|
-
grid-template-columns: 1fr
|
|
163
|
+
grid-template-columns: 1fr 2fr;
|
|
129
164
|
gap: 14px;
|
|
130
165
|
margin-bottom: 14px;
|
|
131
166
|
}
|
|
@@ -197,6 +232,35 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
197
232
|
border: 1px solid rgba(188,140,255,.25);
|
|
198
233
|
}
|
|
199
234
|
.arrow { color: var(--text-secondary); margin: 0 4px; }
|
|
235
|
+
/* ── 时间区间选择栏 ── */
|
|
236
|
+
.time-range-bar {
|
|
237
|
+
display: flex; align-items: center; gap: 8px;
|
|
238
|
+
margin-bottom: 16px; flex-wrap: wrap;
|
|
239
|
+
}
|
|
240
|
+
.time-range-label { font-size: 13px; color: var(--text-secondary); }
|
|
241
|
+
.range-btn {
|
|
242
|
+
padding: 4px 14px; border-radius: 14px;
|
|
243
|
+
background: rgba(48,54,61,.6);
|
|
244
|
+
border: 1px solid var(--border);
|
|
245
|
+
color: var(--text-secondary);
|
|
246
|
+
font-size: 12px; cursor: pointer;
|
|
247
|
+
transition: background .15s, color .15s, border-color .15s;
|
|
248
|
+
}
|
|
249
|
+
.range-btn:hover { background: var(--bg-card-hover); color: var(--text-primary); }
|
|
250
|
+
.range-btn.active {
|
|
251
|
+
background: rgba(88,166,255,.15);
|
|
252
|
+
border-color: rgba(88,166,255,.5);
|
|
253
|
+
color: var(--accent-blue);
|
|
254
|
+
}
|
|
255
|
+
.range-custom { display: none; align-items: center; gap: 6px; }
|
|
256
|
+
.range-custom.visible { display: flex; }
|
|
257
|
+
.range-date {
|
|
258
|
+
padding: 3px 8px; border-radius: var(--radius);
|
|
259
|
+
background: var(--bg-card); border: 1px solid var(--border);
|
|
260
|
+
color: var(--text-primary); font-size: 12px;
|
|
261
|
+
color-scheme: dark;
|
|
262
|
+
}
|
|
263
|
+
.range-sep { font-size: 12px; color: var(--text-secondary); }
|
|
200
264
|
/* ── 空态 ── */
|
|
201
265
|
.empty {
|
|
202
266
|
text-align: center; padding: 32px;
|
|
@@ -210,7 +274,7 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
210
274
|
<header>
|
|
211
275
|
<div class="header-left">
|
|
212
276
|
<div class="logo">C</div>
|
|
213
|
-
<h1>
|
|
277
|
+
<h1>Coding Proxy Dashboard</h1>
|
|
214
278
|
<span class="badge" id="version-badge">v-.-.-</span>
|
|
215
279
|
</div>
|
|
216
280
|
<div class="header-right">
|
|
@@ -220,6 +284,19 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
220
284
|
</header>
|
|
221
285
|
|
|
222
286
|
<main>
|
|
287
|
+
<!-- 时间区间选择器 -->
|
|
288
|
+
<div class="time-range-bar">
|
|
289
|
+
<span class="time-range-label">数据时间区间:</span>
|
|
290
|
+
<button class="range-btn active" onclick="setTimeRange(7, this)">最近一周</button>
|
|
291
|
+
<button class="range-btn" onclick="setTimeRange(30, this)">最近一月</button>
|
|
292
|
+
<button class="range-btn" onclick="setTimeRange(0, this)">自选区间</button>
|
|
293
|
+
<div class="range-custom" id="range-custom">
|
|
294
|
+
<input type="date" id="range-start" class="range-date" onchange="applyCustomRange()" />
|
|
295
|
+
<span class="range-sep">–</span>
|
|
296
|
+
<input type="date" id="range-end" class="range-date" onchange="applyCustomRange()" />
|
|
297
|
+
</div>
|
|
298
|
+
</div>
|
|
299
|
+
|
|
223
300
|
<!-- KPI 卡片 -->
|
|
224
301
|
<div class="kpi-grid" id="kpi-grid">
|
|
225
302
|
<div class="kpi-card"><div class="kpi-label">今日请求数</div><div class="kpi-value color-blue" id="kpi-req-today">–</div><div class="kpi-sub" id="kpi-req-week">本周 –</div></div>
|
|
@@ -239,25 +316,25 @@ _DASHBOARD_HTML = """<!DOCTYPE html>
|
|
|
239
316
|
</div>
|
|
240
317
|
</div>
|
|
241
318
|
<div class="card">
|
|
242
|
-
<div class="card-title">近 7 天请求量趋势</div>
|
|
319
|
+
<div class="card-title" id="title-timeline">近 7 天请求量趋势</div>
|
|
243
320
|
<div class="chart-wrap-lg">
|
|
244
321
|
<canvas id="chart-timeline"></canvas>
|
|
245
322
|
</div>
|
|
246
323
|
</div>
|
|
247
324
|
</div>
|
|
248
325
|
|
|
249
|
-
<!-- 供应商分布 + Token
|
|
326
|
+
<!-- 供应商分布 + Token 量趋势 -->
|
|
250
327
|
<div class="charts-grid-3">
|
|
251
328
|
<div class="card">
|
|
252
|
-
<div class="card-title">供应商请求分布(近 7 天)</div>
|
|
329
|
+
<div class="card-title" id="title-vendor-dist">供应商请求分布(近 7 天)</div>
|
|
253
330
|
<div class="chart-wrap">
|
|
254
331
|
<canvas id="chart-vendor-dist"></canvas>
|
|
255
332
|
</div>
|
|
256
333
|
</div>
|
|
257
334
|
<div class="card">
|
|
258
|
-
<div class="card-title"
|
|
259
|
-
<div class="chart-wrap">
|
|
260
|
-
<canvas id="chart-token-
|
|
335
|
+
<div class="card-title" id="title-token-timeline">近 7 天 Token 量趋势</div>
|
|
336
|
+
<div class="chart-wrap-lg">
|
|
337
|
+
<canvas id="chart-token-timeline"></canvas>
|
|
261
338
|
</div>
|
|
262
339
|
</div>
|
|
263
340
|
</div>
|
|
@@ -317,11 +394,11 @@ Chart.defaults.font.size = 12;
|
|
|
317
394
|
// ── 图表实例 ──────────────────────────────────────────────
|
|
318
395
|
let chartTimeline = null;
|
|
319
396
|
let chartVendorDist = null;
|
|
320
|
-
let
|
|
397
|
+
let chartTokenTimeline = null;
|
|
321
398
|
|
|
322
399
|
function destroyCharts() {
|
|
323
|
-
[chartTimeline, chartVendorDist,
|
|
324
|
-
chartTimeline = chartVendorDist =
|
|
400
|
+
[chartTimeline, chartVendorDist, chartTokenTimeline].forEach(c => c && c.destroy());
|
|
401
|
+
chartTimeline = chartVendorDist = chartTokenTimeline = null;
|
|
325
402
|
}
|
|
326
403
|
|
|
327
404
|
// ── 数据拉取 ──────────────────────────────────────────────
|
|
@@ -507,49 +584,52 @@ function buildVendorDist(rows) {
|
|
|
507
584
|
});
|
|
508
585
|
}
|
|
509
586
|
|
|
510
|
-
// ── Token
|
|
511
|
-
function
|
|
512
|
-
// 按 date 汇总 token
|
|
513
|
-
const
|
|
587
|
+
// ── Token 量趋势折线图 ────────────────────────────────────
|
|
588
|
+
function buildTokenTimeline(rows) {
|
|
589
|
+
// 按 vendor 分组,按 date 汇总 token 总量
|
|
590
|
+
const vendorDateMap = {}; // vendor → {date → total_tokens}
|
|
514
591
|
const allDates = new Set();
|
|
515
592
|
for (const r of rows) {
|
|
516
|
-
const d = r.date;
|
|
517
|
-
if (!d) continue;
|
|
518
|
-
if (!
|
|
519
|
-
|
|
520
|
-
|
|
521
|
-
|
|
522
|
-
byDate[d].cache_read += r.total_cache_read || 0;
|
|
593
|
+
const v = r.vendor, d = r.date;
|
|
594
|
+
if (!v || !d) continue;
|
|
595
|
+
if (!vendorDateMap[v]) vendorDateMap[v] = {};
|
|
596
|
+
const total = (r.total_input || 0) + (r.total_output || 0)
|
|
597
|
+
+ (r.total_cache_creation || 0) + (r.total_cache_read || 0);
|
|
598
|
+
vendorDateMap[v][d] = (vendorDateMap[v][d] || 0) + total;
|
|
523
599
|
allDates.add(d);
|
|
524
600
|
}
|
|
525
601
|
const dates = [...allDates].sort();
|
|
602
|
+
const vendors = Object.keys(vendorDateMap).sort();
|
|
526
603
|
|
|
527
|
-
if (
|
|
528
|
-
const ctx = document.getElementById('chart-token-
|
|
604
|
+
if (chartTokenTimeline) chartTokenTimeline.destroy();
|
|
605
|
+
const ctx = document.getElementById('chart-token-timeline').getContext('2d');
|
|
529
606
|
if (!dates.length) {
|
|
530
607
|
ctx.canvas.parentElement.innerHTML = '<div class="empty">暂无数据</div>';
|
|
531
608
|
return;
|
|
532
609
|
}
|
|
533
610
|
|
|
534
|
-
const
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
backgroundColor:
|
|
539
|
-
|
|
540
|
-
|
|
611
|
+
const datasets = vendors.map((v, i) => ({
|
|
612
|
+
label: v,
|
|
613
|
+
data: dates.map(d => vendorDateMap[v][d] || 0),
|
|
614
|
+
borderColor: VENDOR_COLORS[i % VENDOR_COLORS.length],
|
|
615
|
+
backgroundColor: VENDOR_COLORS[i % VENDOR_COLORS.length] + '22',
|
|
616
|
+
fill: true,
|
|
617
|
+
tension: .3,
|
|
618
|
+
pointRadius: 3,
|
|
619
|
+
pointHoverRadius: 5,
|
|
541
620
|
}));
|
|
542
621
|
|
|
543
|
-
|
|
544
|
-
type: '
|
|
622
|
+
chartTokenTimeline = new Chart(ctx, {
|
|
623
|
+
type: 'line',
|
|
545
624
|
data: { labels: dates, datasets },
|
|
546
625
|
options: {
|
|
547
626
|
responsive: true, maintainAspectRatio: false,
|
|
548
|
-
|
|
627
|
+
interaction: { mode: 'index', intersect: false },
|
|
628
|
+
plugins: { legend: { position: 'bottom', labels: { boxWidth: 10, padding: 12 } } },
|
|
549
629
|
scales: {
|
|
550
|
-
x: {
|
|
630
|
+
x: { grid: { color: '#30363d' } },
|
|
551
631
|
y: {
|
|
552
|
-
|
|
632
|
+
grid: { color: '#30363d' }, beginAtZero: true,
|
|
553
633
|
ticks: { callback: v => fmtTokens(v) },
|
|
554
634
|
},
|
|
555
635
|
},
|
|
@@ -572,6 +652,56 @@ function updateFtTable(failoverStats) {
|
|
|
572
652
|
</tr>`).join('');
|
|
573
653
|
}
|
|
574
654
|
|
|
655
|
+
// ── 时间区间控制 ──────────────────────────────────────────
|
|
656
|
+
let currentDays = 7;
|
|
657
|
+
|
|
658
|
+
function setTimeRange(days, btn) {
|
|
659
|
+
currentDays = days;
|
|
660
|
+
document.querySelectorAll('.range-btn').forEach(b => b.classList.remove('active'));
|
|
661
|
+
if (btn) btn.classList.add('active');
|
|
662
|
+
const customEl = document.getElementById('range-custom');
|
|
663
|
+
if (days === 0) {
|
|
664
|
+
customEl.classList.add('visible');
|
|
665
|
+
// 初始化日期:默认今天往前 7 天
|
|
666
|
+
const today = new Date();
|
|
667
|
+
const weekAgo = new Date(today);
|
|
668
|
+
weekAgo.setDate(weekAgo.getDate() - 6);
|
|
669
|
+
document.getElementById('range-end').value = today.toISOString().slice(0, 10);
|
|
670
|
+
document.getElementById('range-start').value = weekAgo.toISOString().slice(0, 10);
|
|
671
|
+
applyCustomRange();
|
|
672
|
+
} else {
|
|
673
|
+
customEl.classList.remove('visible');
|
|
674
|
+
refresh();
|
|
675
|
+
}
|
|
676
|
+
}
|
|
677
|
+
|
|
678
|
+
function applyCustomRange() {
|
|
679
|
+
const s = document.getElementById('range-start').value;
|
|
680
|
+
const e = document.getElementById('range-end').value;
|
|
681
|
+
if (!s || !e) return;
|
|
682
|
+
const startMs = new Date(s).getTime();
|
|
683
|
+
const endMs = new Date(e).getTime();
|
|
684
|
+
if (endMs < startMs) return;
|
|
685
|
+
currentDays = Math.ceil((endMs - startMs) / 86400000) + 1;
|
|
686
|
+
refresh();
|
|
687
|
+
}
|
|
688
|
+
|
|
689
|
+
function rangeLabel() {
|
|
690
|
+
if (currentDays <= 7) return '近 7 天';
|
|
691
|
+
if (currentDays <= 30) return '近 30 天';
|
|
692
|
+
return '近 ' + currentDays + ' 天';
|
|
693
|
+
}
|
|
694
|
+
|
|
695
|
+
function updateChartTitles(days) {
|
|
696
|
+
const label = days <= 7 ? '近 7 天' : (days <= 30 ? '近 30 天' : '近 ' + days + ' 天');
|
|
697
|
+
const tl = document.getElementById('title-timeline');
|
|
698
|
+
const tt = document.getElementById('title-token-timeline');
|
|
699
|
+
const vd = document.getElementById('title-vendor-dist');
|
|
700
|
+
if (tl) tl.textContent = label + ' 请求量趋势';
|
|
701
|
+
if (tt) tt.textContent = label + ' Token 量趋势';
|
|
702
|
+
if (vd) vd.textContent = '供应商请求分布(' + label + ')';
|
|
703
|
+
}
|
|
704
|
+
|
|
575
705
|
// ── 主刷新逻辑 ────────────────────────────────────────────
|
|
576
706
|
let refreshing = false;
|
|
577
707
|
async function refresh() {
|
|
@@ -579,9 +709,10 @@ async function refresh() {
|
|
|
579
709
|
refreshing = true;
|
|
580
710
|
document.getElementById('refresh-time').textContent = '刷新中…';
|
|
581
711
|
try {
|
|
712
|
+
const days = currentDays > 0 ? currentDays : 7;
|
|
582
713
|
const [summary, timeline, status] = await Promise.all([
|
|
583
714
|
fetchJSON('/api/dashboard/summary'),
|
|
584
|
-
fetchJSON('/api/dashboard/timeline?days=
|
|
715
|
+
fetchJSON('/api/dashboard/timeline?days=' + days),
|
|
585
716
|
fetchJSON('/api/status'),
|
|
586
717
|
]);
|
|
587
718
|
|
|
@@ -592,11 +723,12 @@ async function refresh() {
|
|
|
592
723
|
|
|
593
724
|
updateKPI(summary);
|
|
594
725
|
updateVendorStatus(status);
|
|
726
|
+
updateChartTitles(days);
|
|
595
727
|
|
|
596
728
|
const rows = timeline.rows || [];
|
|
597
729
|
buildTimeline(rows);
|
|
598
730
|
buildVendorDist(rows);
|
|
599
|
-
|
|
731
|
+
buildTokenTimeline(rows);
|
|
600
732
|
|
|
601
733
|
updateFtTable(summary.failover_stats || []);
|
|
602
734
|
|
|
@@ -687,6 +819,11 @@ def register_dashboard_routes(app: Any) -> None:
|
|
|
687
819
|
"""注册 Dashboard 相关路由."""
|
|
688
820
|
from .. import __version__
|
|
689
821
|
|
|
822
|
+
@app.get("/favicon.ico", include_in_schema=False)
|
|
823
|
+
async def favicon() -> Response:
|
|
824
|
+
"""返回内嵌 favicon."""
|
|
825
|
+
return Response(content=_FAVICON_ICO, media_type="image/x-icon")
|
|
826
|
+
|
|
690
827
|
@app.get("/dashboard", response_class=HTMLResponse, include_in_schema=False)
|
|
691
828
|
async def dashboard() -> HTMLResponse:
|
|
692
829
|
"""返回 Dashboard HTML 页面."""
|
|
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
|
{coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/convert/anthropic_to_gemini.py
RENAMED
|
File without changes
|
{coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/convert/anthropic_to_openai.py
RENAMED
|
File without changes
|
{coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/convert/gemini_sse_adapter.py
RENAMED
|
File without changes
|
{coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/src/coding/proxy/convert/gemini_to_anthropic.py
RENAMED
|
File without changes
|
{coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{coding_proxy-0.2.3a1 → coding_proxy-0.2.3a2}/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.3a1 → coding_proxy-0.2.3a2}/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
|