llmapi-v2 2.1.0
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.
- package/.env.example +40 -0
- package/Dockerfile +17 -0
- package/dist/config.d.ts +48 -0
- package/dist/config.js +98 -0
- package/dist/config.js.map +1 -0
- package/dist/converter/request.d.ts +6 -0
- package/dist/converter/request.js +184 -0
- package/dist/converter/request.js.map +1 -0
- package/dist/converter/response.d.ts +6 -0
- package/dist/converter/response.js +76 -0
- package/dist/converter/response.js.map +1 -0
- package/dist/converter/stream.d.ts +54 -0
- package/dist/converter/stream.js +318 -0
- package/dist/converter/stream.js.map +1 -0
- package/dist/converter/types.d.ts +239 -0
- package/dist/converter/types.js +6 -0
- package/dist/converter/types.js.map +1 -0
- package/dist/data/posts.d.ts +19 -0
- package/dist/data/posts.js +462 -0
- package/dist/data/posts.js.map +1 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +233 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/api-key-auth.d.ts +6 -0
- package/dist/middleware/api-key-auth.js +76 -0
- package/dist/middleware/api-key-auth.js.map +1 -0
- package/dist/middleware/quota-guard.d.ts +10 -0
- package/dist/middleware/quota-guard.js +27 -0
- package/dist/middleware/quota-guard.js.map +1 -0
- package/dist/middleware/rate-limiter.d.ts +5 -0
- package/dist/middleware/rate-limiter.js +50 -0
- package/dist/middleware/rate-limiter.js.map +1 -0
- package/dist/middleware/request-logger.d.ts +6 -0
- package/dist/middleware/request-logger.js +37 -0
- package/dist/middleware/request-logger.js.map +1 -0
- package/dist/middleware/session-auth.d.ts +19 -0
- package/dist/middleware/session-auth.js +99 -0
- package/dist/middleware/session-auth.js.map +1 -0
- package/dist/providers/aliyun.d.ts +13 -0
- package/dist/providers/aliyun.js +20 -0
- package/dist/providers/aliyun.js.map +1 -0
- package/dist/providers/base-provider.d.ts +36 -0
- package/dist/providers/base-provider.js +133 -0
- package/dist/providers/base-provider.js.map +1 -0
- package/dist/providers/deepseek.d.ts +11 -0
- package/dist/providers/deepseek.js +18 -0
- package/dist/providers/deepseek.js.map +1 -0
- package/dist/providers/registry.d.ts +18 -0
- package/dist/providers/registry.js +98 -0
- package/dist/providers/registry.js.map +1 -0
- package/dist/providers/types.d.ts +17 -0
- package/dist/providers/types.js +3 -0
- package/dist/providers/types.js.map +1 -0
- package/dist/routes/admin.d.ts +1 -0
- package/dist/routes/admin.js +153 -0
- package/dist/routes/admin.js.map +1 -0
- package/dist/routes/auth.d.ts +2 -0
- package/dist/routes/auth.js +318 -0
- package/dist/routes/auth.js.map +1 -0
- package/dist/routes/blog.d.ts +1 -0
- package/dist/routes/blog.js +29 -0
- package/dist/routes/blog.js.map +1 -0
- package/dist/routes/dashboard.d.ts +1 -0
- package/dist/routes/dashboard.js +184 -0
- package/dist/routes/dashboard.js.map +1 -0
- package/dist/routes/messages.d.ts +1 -0
- package/dist/routes/messages.js +309 -0
- package/dist/routes/messages.js.map +1 -0
- package/dist/routes/models.d.ts +1 -0
- package/dist/routes/models.js +39 -0
- package/dist/routes/models.js.map +1 -0
- package/dist/routes/payment.d.ts +1 -0
- package/dist/routes/payment.js +150 -0
- package/dist/routes/payment.js.map +1 -0
- package/dist/routes/sitemap.d.ts +1 -0
- package/dist/routes/sitemap.js +38 -0
- package/dist/routes/sitemap.js.map +1 -0
- package/dist/services/alipay.d.ts +27 -0
- package/dist/services/alipay.js +106 -0
- package/dist/services/alipay.js.map +1 -0
- package/dist/services/database.d.ts +4 -0
- package/dist/services/database.js +170 -0
- package/dist/services/database.js.map +1 -0
- package/dist/services/health-checker.d.ts +13 -0
- package/dist/services/health-checker.js +95 -0
- package/dist/services/health-checker.js.map +1 -0
- package/dist/services/mailer.d.ts +3 -0
- package/dist/services/mailer.js +91 -0
- package/dist/services/mailer.js.map +1 -0
- package/dist/services/metrics.d.ts +56 -0
- package/dist/services/metrics.js +94 -0
- package/dist/services/metrics.js.map +1 -0
- package/dist/services/remote-control.d.ts +20 -0
- package/dist/services/remote-control.js +209 -0
- package/dist/services/remote-control.js.map +1 -0
- package/dist/services/remote-ws.d.ts +5 -0
- package/dist/services/remote-ws.js +143 -0
- package/dist/services/remote-ws.js.map +1 -0
- package/dist/services/usage.d.ts +13 -0
- package/dist/services/usage.js +39 -0
- package/dist/services/usage.js.map +1 -0
- package/dist/utils/errors.d.ts +27 -0
- package/dist/utils/errors.js +48 -0
- package/dist/utils/errors.js.map +1 -0
- package/dist/utils/logger.d.ts +2 -0
- package/dist/utils/logger.js +14 -0
- package/dist/utils/logger.js.map +1 -0
- package/docker-compose.yml +19 -0
- package/package.json +39 -0
- package/public/robots.txt +8 -0
- package/src/config.ts +140 -0
- package/src/converter/request.ts +207 -0
- package/src/converter/response.ts +85 -0
- package/src/converter/stream.ts +373 -0
- package/src/converter/types.ts +257 -0
- package/src/data/posts.ts +474 -0
- package/src/index.ts +219 -0
- package/src/middleware/api-key-auth.ts +82 -0
- package/src/middleware/quota-guard.ts +28 -0
- package/src/middleware/rate-limiter.ts +61 -0
- package/src/middleware/request-logger.ts +36 -0
- package/src/middleware/session-auth.ts +91 -0
- package/src/providers/aliyun.ts +16 -0
- package/src/providers/base-provider.ts +148 -0
- package/src/providers/deepseek.ts +14 -0
- package/src/providers/registry.ts +111 -0
- package/src/providers/types.ts +26 -0
- package/src/routes/admin.ts +169 -0
- package/src/routes/auth.ts +369 -0
- package/src/routes/blog.ts +28 -0
- package/src/routes/dashboard.ts +208 -0
- package/src/routes/messages.ts +346 -0
- package/src/routes/models.ts +37 -0
- package/src/routes/payment.ts +189 -0
- package/src/routes/sitemap.ts +40 -0
- package/src/services/alipay.ts +116 -0
- package/src/services/database.ts +187 -0
- package/src/services/health-checker.ts +115 -0
- package/src/services/mailer.ts +90 -0
- package/src/services/metrics.ts +104 -0
- package/src/services/remote-control.ts +226 -0
- package/src/services/remote-ws.ts +145 -0
- package/src/services/usage.ts +57 -0
- package/src/types/express.d.ts +46 -0
- package/src/utils/errors.ts +44 -0
- package/src/utils/logger.ts +8 -0
- package/tsconfig.json +17 -0
- package/views/pages/404.ejs +14 -0
- package/views/pages/admin.ejs +307 -0
- package/views/pages/blog-post.ejs +378 -0
- package/views/pages/blog.ejs +148 -0
- package/views/pages/dashboard.ejs +441 -0
- package/views/pages/docs.ejs +807 -0
- package/views/pages/index.ejs +416 -0
- package/views/pages/login.ejs +170 -0
- package/views/pages/orders.ejs +111 -0
- package/views/pages/pricing.ejs +379 -0
- package/views/pages/register.ejs +397 -0
- package/views/pages/remote.ejs +334 -0
- package/views/pages/settings.ejs +373 -0
- package/views/partials/header.ejs +70 -0
- package/views/partials/nav.ejs +140 -0
|
@@ -0,0 +1,441 @@
|
|
|
1
|
+
<%- include('../partials/header', { pageTitle: '控制台' }) %>
|
|
2
|
+
<%- include('../partials/nav') %>
|
|
3
|
+
|
|
4
|
+
<div class="max-w-7xl mx-auto px-4 sm:px-6 lg:px-8 py-8">
|
|
5
|
+
|
|
6
|
+
<!-- 页面标题 -->
|
|
7
|
+
<div class="mb-8">
|
|
8
|
+
<h1 class="text-2xl font-bold text-claude-dark">控制台</h1>
|
|
9
|
+
<p class="text-sm text-gray-500 mt-1">管理你的 API 密钥、查看用量统计</p>
|
|
10
|
+
</div>
|
|
11
|
+
|
|
12
|
+
<!-- 统计卡片 -->
|
|
13
|
+
<div class="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-4 gap-5 mb-8">
|
|
14
|
+
<!-- Token 用量 -->
|
|
15
|
+
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
|
16
|
+
<div class="flex items-center justify-between mb-3">
|
|
17
|
+
<span class="text-sm font-medium text-gray-500">Token 总用量</span>
|
|
18
|
+
<svg class="w-5 h-5 text-claude-orange" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M13 10V3L4 14h7v7l9-11h-7z"/></svg>
|
|
19
|
+
</div>
|
|
20
|
+
<div class="text-2xl font-bold text-claude-dark" id="statTokens">--</div>
|
|
21
|
+
<div class="mt-3">
|
|
22
|
+
<div class="flex items-center justify-between text-xs text-gray-500 mb-1">
|
|
23
|
+
<span>已用 / 总额度</span>
|
|
24
|
+
<span id="statTokenPercent">0%</span>
|
|
25
|
+
</div>
|
|
26
|
+
<div class="w-full bg-gray-100 rounded-full h-2">
|
|
27
|
+
<div class="bg-claude-orange rounded-full h-2 transition-all duration-500" id="statTokenBar" style="width: 0%"></div>
|
|
28
|
+
</div>
|
|
29
|
+
</div>
|
|
30
|
+
</div>
|
|
31
|
+
|
|
32
|
+
<!-- 今日请求 -->
|
|
33
|
+
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
|
34
|
+
<div class="flex items-center justify-between mb-3">
|
|
35
|
+
<span class="text-sm font-medium text-gray-500">今日请求</span>
|
|
36
|
+
<svg class="w-5 h-5 text-blue-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 19v-6a2 2 0 00-2-2H5a2 2 0 00-2 2v6a2 2 0 002 2h2a2 2 0 002-2zm0 0V9a2 2 0 012-2h2a2 2 0 012 2v10m-6 0a2 2 0 002 2h2a2 2 0 002-2m0 0V5a2 2 0 012-2h2a2 2 0 012 2v14a2 2 0 01-2 2h-2a2 2 0 01-2-2z"/></svg>
|
|
37
|
+
</div>
|
|
38
|
+
<div class="text-2xl font-bold text-claude-dark" id="statRequests">--</div>
|
|
39
|
+
<p class="text-xs text-gray-400 mt-2">较昨日 <span id="statRequestsDiff" class="text-green-500">--</span></p>
|
|
40
|
+
</div>
|
|
41
|
+
|
|
42
|
+
<!-- 当前套餐 -->
|
|
43
|
+
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
|
44
|
+
<div class="flex items-center justify-between mb-3">
|
|
45
|
+
<span class="text-sm font-medium text-gray-500">当前套餐</span>
|
|
46
|
+
<svg class="w-5 h-5 text-purple-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M5 3v4M3 5h4M6 17v4m-2-2h4m5-16l2.286 6.857L21 12l-5.714 2.143L13 21l-2.286-6.857L5 12l5.714-2.143L13 3z"/></svg>
|
|
47
|
+
</div>
|
|
48
|
+
<div class="text-2xl font-bold text-claude-dark" id="statPlan">--</div>
|
|
49
|
+
<a href="/pricing" class="inline-block text-xs text-claude-orange hover:underline mt-2">升级套餐 →</a>
|
|
50
|
+
</div>
|
|
51
|
+
|
|
52
|
+
<!-- API Key 数量 -->
|
|
53
|
+
<div class="bg-white rounded-xl border border-gray-100 p-5 shadow-sm">
|
|
54
|
+
<div class="flex items-center justify-between mb-3">
|
|
55
|
+
<span class="text-sm font-medium text-gray-500">API 密钥</span>
|
|
56
|
+
<svg class="w-5 h-5 text-green-500" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M15 7a2 2 0 012 2m4 0a6 6 0 01-7.743 5.743L11 17H9v2H7v2H4a1 1 0 01-1-1v-2.586a1 1 0 01.293-.707l5.964-5.964A6 6 0 1121 9z"/></svg>
|
|
57
|
+
</div>
|
|
58
|
+
<div class="text-2xl font-bold text-claude-dark" id="statKeys">--</div>
|
|
59
|
+
<p class="text-xs text-gray-400 mt-2">活跃密钥数</p>
|
|
60
|
+
</div>
|
|
61
|
+
</div>
|
|
62
|
+
|
|
63
|
+
<!-- Token 明细 + 7 日用量图表 -->
|
|
64
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6 mb-8">
|
|
65
|
+
|
|
66
|
+
<!-- Token 明细 -->
|
|
67
|
+
<div class="bg-white rounded-xl border border-gray-100 p-6 shadow-sm">
|
|
68
|
+
<h2 class="text-lg font-semibold text-claude-dark mb-4">Token 明细</h2>
|
|
69
|
+
<div class="space-y-4" id="tokenBreakdown">
|
|
70
|
+
<div class="flex items-center justify-between">
|
|
71
|
+
<div class="flex items-center space-x-2">
|
|
72
|
+
<span class="w-3 h-3 rounded-full bg-claude-orange"></span>
|
|
73
|
+
<span class="text-sm text-gray-600">输入 Tokens</span>
|
|
74
|
+
</div>
|
|
75
|
+
<span class="text-sm font-semibold" id="inputTokens">--</span>
|
|
76
|
+
</div>
|
|
77
|
+
<div class="w-full bg-gray-100 rounded-full h-2">
|
|
78
|
+
<div class="bg-claude-orange rounded-full h-2 transition-all duration-500" id="inputTokenBar" style="width: 0%"></div>
|
|
79
|
+
</div>
|
|
80
|
+
|
|
81
|
+
<div class="flex items-center justify-between">
|
|
82
|
+
<div class="flex items-center space-x-2">
|
|
83
|
+
<span class="w-3 h-3 rounded-full bg-blue-500"></span>
|
|
84
|
+
<span class="text-sm text-gray-600">输出 Tokens</span>
|
|
85
|
+
</div>
|
|
86
|
+
<span class="text-sm font-semibold" id="outputTokens">--</span>
|
|
87
|
+
</div>
|
|
88
|
+
<div class="w-full bg-gray-100 rounded-full h-2">
|
|
89
|
+
<div class="bg-blue-500 rounded-full h-2 transition-all duration-500" id="outputTokenBar" style="width: 0%"></div>
|
|
90
|
+
</div>
|
|
91
|
+
|
|
92
|
+
<div class="flex items-center justify-between">
|
|
93
|
+
<div class="flex items-center space-x-2">
|
|
94
|
+
<span class="w-3 h-3 rounded-full bg-purple-500"></span>
|
|
95
|
+
<span class="text-sm text-gray-600">思考 Tokens</span>
|
|
96
|
+
</div>
|
|
97
|
+
<span class="text-sm font-semibold" id="thinkingTokens">--</span>
|
|
98
|
+
</div>
|
|
99
|
+
<div class="w-full bg-gray-100 rounded-full h-2">
|
|
100
|
+
<div class="bg-purple-500 rounded-full h-2 transition-all duration-500" id="thinkingTokenBar" style="width: 0%"></div>
|
|
101
|
+
</div>
|
|
102
|
+
</div>
|
|
103
|
+
</div>
|
|
104
|
+
|
|
105
|
+
<!-- 7 日用量图表 -->
|
|
106
|
+
<div class="bg-white rounded-xl border border-gray-100 p-6 shadow-sm">
|
|
107
|
+
<h2 class="text-lg font-semibold text-claude-dark mb-4">近 7 日用量</h2>
|
|
108
|
+
<div class="flex items-center space-x-4 text-xs text-gray-500 mb-4">
|
|
109
|
+
<div class="flex items-center space-x-1"><span class="w-3 h-3 rounded-sm bg-claude-orange"></span><span>输入</span></div>
|
|
110
|
+
<div class="flex items-center space-x-1"><span class="w-3 h-3 rounded-sm bg-blue-500"></span><span>输出</span></div>
|
|
111
|
+
</div>
|
|
112
|
+
<div class="flex items-end justify-between space-x-2" id="usageChart" style="height: 180px;">
|
|
113
|
+
<!-- 动态填充 -->
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<!-- API 密钥管理 -->
|
|
119
|
+
<div class="bg-white rounded-xl border border-gray-100 p-6 shadow-sm mb-8">
|
|
120
|
+
<div class="flex items-center justify-between mb-6">
|
|
121
|
+
<h2 class="text-lg font-semibold text-claude-dark">API 密钥管理</h2>
|
|
122
|
+
</div>
|
|
123
|
+
|
|
124
|
+
<!-- 创建密钥 -->
|
|
125
|
+
<div class="flex items-center space-x-3 mb-6">
|
|
126
|
+
<input
|
|
127
|
+
type="text"
|
|
128
|
+
id="newKeyName"
|
|
129
|
+
placeholder="密钥名称(如 my-project)"
|
|
130
|
+
class="flex-1 max-w-xs px-4 py-2 text-sm border border-gray-200 rounded-lg focus:outline-none focus:ring-2 focus:ring-claude-orange/40 focus:border-claude-orange"
|
|
131
|
+
/>
|
|
132
|
+
<button
|
|
133
|
+
onclick="createApiKey()"
|
|
134
|
+
class="inline-flex items-center px-4 py-2 text-sm font-medium text-white bg-claude-orange rounded-lg hover:bg-opacity-90 transition-all shadow-sm"
|
|
135
|
+
>
|
|
136
|
+
<svg class="w-4 h-4 mr-1.5" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 4v16m8-8H4"/></svg>
|
|
137
|
+
创建密钥
|
|
138
|
+
</button>
|
|
139
|
+
</div>
|
|
140
|
+
|
|
141
|
+
<!-- 新密钥展示 -->
|
|
142
|
+
<div id="newKeyDisplay" class="hidden mb-6 p-4 bg-green-50 border border-green-200 rounded-lg">
|
|
143
|
+
<div class="flex items-start space-x-3">
|
|
144
|
+
<svg class="w-5 h-5 text-green-500 mt-0.5 flex-shrink-0" fill="none" stroke="currentColor" viewBox="0 0 24 24"><path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z"/></svg>
|
|
145
|
+
<div class="flex-1 min-w-0">
|
|
146
|
+
<p class="text-sm font-medium text-green-800 mb-1">密钥创建成功!请立即复制保存,此密钥仅显示一次。</p>
|
|
147
|
+
<div class="flex items-center space-x-2">
|
|
148
|
+
<code class="text-sm bg-white px-3 py-1.5 rounded border border-green-200 font-mono break-all flex-1" id="newKeyValue"></code>
|
|
149
|
+
<button onclick="copyNewKey()" class="flex-shrink-0 px-3 py-1.5 text-xs font-medium text-green-700 bg-green-100 rounded hover:bg-green-200 transition-colors">复制</button>
|
|
150
|
+
</div>
|
|
151
|
+
</div>
|
|
152
|
+
</div>
|
|
153
|
+
</div>
|
|
154
|
+
|
|
155
|
+
<!-- 密钥列表 -->
|
|
156
|
+
<div class="overflow-x-auto">
|
|
157
|
+
<table class="w-full text-sm">
|
|
158
|
+
<thead>
|
|
159
|
+
<tr class="border-b border-gray-100">
|
|
160
|
+
<th class="text-left py-3 px-2 font-medium text-gray-500">名称</th>
|
|
161
|
+
<th class="text-left py-3 px-2 font-medium text-gray-500">密钥</th>
|
|
162
|
+
<th class="text-left py-3 px-2 font-medium text-gray-500">创建时间</th>
|
|
163
|
+
<th class="text-left py-3 px-2 font-medium text-gray-500">最后使用</th>
|
|
164
|
+
<th class="text-right py-3 px-2 font-medium text-gray-500">操作</th>
|
|
165
|
+
</tr>
|
|
166
|
+
</thead>
|
|
167
|
+
<tbody id="apiKeysList">
|
|
168
|
+
<tr><td colspan="5" class="text-center py-8 text-gray-400">加载中...</td></tr>
|
|
169
|
+
</tbody>
|
|
170
|
+
</table>
|
|
171
|
+
</div>
|
|
172
|
+
</div>
|
|
173
|
+
|
|
174
|
+
<!-- 快速开始 -->
|
|
175
|
+
<div class="bg-white rounded-xl border border-gray-100 p-6 shadow-sm mb-8">
|
|
176
|
+
<h2 class="text-lg font-semibold text-claude-dark mb-4">快速开始</h2>
|
|
177
|
+
|
|
178
|
+
<div class="grid grid-cols-1 lg:grid-cols-2 gap-6">
|
|
179
|
+
<!-- Claude Code 配置 -->
|
|
180
|
+
<div>
|
|
181
|
+
<h3 class="text-sm font-medium text-gray-700 mb-2">Claude Code 配置</h3>
|
|
182
|
+
<p class="text-xs text-gray-500 mb-3">在终端中执行以下命令即可完成配置:</p>
|
|
183
|
+
<div class="relative">
|
|
184
|
+
<pre class="bg-gray-900 text-green-400 rounded-lg p-4 text-xs font-mono overflow-x-auto leading-relaxed"><code id="codeClaudeConfig"># macOS / Linux
|
|
185
|
+
export ANTHROPIC_BASE_URL=https://llmapi.pro
|
|
186
|
+
export ANTHROPIC_API_KEY=你的API密钥
|
|
187
|
+
|
|
188
|
+
# Windows PowerShell
|
|
189
|
+
$env:ANTHROPIC_BASE_URL="https://llmapi.pro"
|
|
190
|
+
$env:ANTHROPIC_API_KEY="你的API密钥"</code></pre>
|
|
191
|
+
<button onclick="copyCode('codeClaudeConfig')" class="absolute top-2 right-2 px-2 py-1 text-xs text-gray-400 bg-gray-800 rounded hover:text-white transition-colors">复制</button>
|
|
192
|
+
</div>
|
|
193
|
+
</div>
|
|
194
|
+
|
|
195
|
+
<!-- Python SDK -->
|
|
196
|
+
<div>
|
|
197
|
+
<h3 class="text-sm font-medium text-gray-700 mb-2">Python SDK</h3>
|
|
198
|
+
<p class="text-xs text-gray-500 mb-3">使用 Anthropic Python SDK 调用 API:</p>
|
|
199
|
+
<div class="relative">
|
|
200
|
+
<pre class="bg-gray-900 text-green-400 rounded-lg p-4 text-xs font-mono overflow-x-auto leading-relaxed"><code id="codePython">import anthropic
|
|
201
|
+
|
|
202
|
+
client = anthropic.Anthropic(
|
|
203
|
+
base_url="https://llmapi.pro",
|
|
204
|
+
api_key="你的API密钥",
|
|
205
|
+
)
|
|
206
|
+
|
|
207
|
+
message = client.messages.create(
|
|
208
|
+
model="claude-sonnet-4-20250514",
|
|
209
|
+
max_tokens=1024,
|
|
210
|
+
messages=[
|
|
211
|
+
{"role": "user", "content": "你好"}
|
|
212
|
+
]
|
|
213
|
+
)
|
|
214
|
+
print(message.content[0].text)</code></pre>
|
|
215
|
+
<button onclick="copyCode('codePython')" class="absolute top-2 right-2 px-2 py-1 text-xs text-gray-400 bg-gray-800 rounded hover:text-white transition-colors">复制</button>
|
|
216
|
+
</div>
|
|
217
|
+
</div>
|
|
218
|
+
</div>
|
|
219
|
+
</div>
|
|
220
|
+
|
|
221
|
+
</div>
|
|
222
|
+
|
|
223
|
+
<script>
|
|
224
|
+
// 格式化数字
|
|
225
|
+
function fmtNum(n) {
|
|
226
|
+
if (n == null) return '--';
|
|
227
|
+
if (n >= 1e9) return (n / 1e9).toFixed(2) + 'B';
|
|
228
|
+
if (n >= 1e6) return (n / 1e6).toFixed(2) + 'M';
|
|
229
|
+
if (n >= 1e3) return (n / 1e3).toFixed(1) + 'K';
|
|
230
|
+
return n.toLocaleString();
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
function fmtDate(d) {
|
|
234
|
+
if (!d) return '--';
|
|
235
|
+
const dt = new Date(d);
|
|
236
|
+
return dt.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' });
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
// 复制功能
|
|
240
|
+
function copyCode(id) {
|
|
241
|
+
const el = document.getElementById(id);
|
|
242
|
+
navigator.clipboard.writeText(el.textContent).then(() => {
|
|
243
|
+
const btn = el.closest('.relative').querySelector('button');
|
|
244
|
+
btn.textContent = '已复制';
|
|
245
|
+
setTimeout(() => btn.textContent = '复制', 2000);
|
|
246
|
+
});
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
function copyNewKey() {
|
|
250
|
+
const val = document.getElementById('newKeyValue').textContent;
|
|
251
|
+
navigator.clipboard.writeText(val).then(() => {
|
|
252
|
+
const btn = document.querySelector('#newKeyDisplay button');
|
|
253
|
+
btn.textContent = '已复制';
|
|
254
|
+
setTimeout(() => btn.textContent = '复制', 2000);
|
|
255
|
+
});
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
// 加载统计数据
|
|
259
|
+
async function loadStats() {
|
|
260
|
+
try {
|
|
261
|
+
const res = await fetch('/api/dashboard/stats', { credentials: 'same-origin' });
|
|
262
|
+
if (!res.ok) throw new Error('请求失败');
|
|
263
|
+
const data = await res.json();
|
|
264
|
+
const s = data.data || data;
|
|
265
|
+
|
|
266
|
+
document.getElementById('statTokens').textContent = fmtNum(s.totalTokens);
|
|
267
|
+
const pct = s.tokenLimit ? Math.min(100, (s.totalTokens / s.tokenLimit * 100)).toFixed(1) : 0;
|
|
268
|
+
document.getElementById('statTokenPercent').textContent = pct + '%';
|
|
269
|
+
document.getElementById('statTokenBar').style.width = pct + '%';
|
|
270
|
+
|
|
271
|
+
document.getElementById('statRequests').textContent = fmtNum(s.todayRequests);
|
|
272
|
+
const diff = s.requestsDiff;
|
|
273
|
+
const diffEl = document.getElementById('statRequestsDiff');
|
|
274
|
+
if (diff != null) {
|
|
275
|
+
diffEl.textContent = (diff >= 0 ? '+' : '') + diff + '%';
|
|
276
|
+
diffEl.className = diff >= 0 ? 'text-green-500' : 'text-red-500';
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
document.getElementById('statPlan').textContent = s.plan || '免费版';
|
|
280
|
+
document.getElementById('statKeys').textContent = s.apiKeyCount ?? '--';
|
|
281
|
+
|
|
282
|
+
// Token 明细
|
|
283
|
+
const total = (s.inputTokens || 0) + (s.outputTokens || 0) + (s.thinkingTokens || 0) || 1;
|
|
284
|
+
document.getElementById('inputTokens').textContent = fmtNum(s.inputTokens);
|
|
285
|
+
document.getElementById('inputTokenBar').style.width = ((s.inputTokens || 0) / total * 100) + '%';
|
|
286
|
+
document.getElementById('outputTokens').textContent = fmtNum(s.outputTokens);
|
|
287
|
+
document.getElementById('outputTokenBar').style.width = ((s.outputTokens || 0) / total * 100) + '%';
|
|
288
|
+
document.getElementById('thinkingTokens').textContent = fmtNum(s.thinkingTokens);
|
|
289
|
+
document.getElementById('thinkingTokenBar').style.width = ((s.thinkingTokens || 0) / total * 100) + '%';
|
|
290
|
+
} catch (e) {
|
|
291
|
+
console.error('加载统计失败:', e);
|
|
292
|
+
}
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
// 加载 7 日用量图表
|
|
296
|
+
async function loadUsage() {
|
|
297
|
+
try {
|
|
298
|
+
const res = await fetch('/api/dashboard/usage', { credentials: 'same-origin' });
|
|
299
|
+
if (!res.ok) throw new Error('请求失败');
|
|
300
|
+
const data = await res.json();
|
|
301
|
+
const days = data.data || data || [];
|
|
302
|
+
|
|
303
|
+
const chart = document.getElementById('usageChart');
|
|
304
|
+
chart.innerHTML = '';
|
|
305
|
+
|
|
306
|
+
if (!days.length) {
|
|
307
|
+
chart.innerHTML = '<div class="flex items-center justify-center w-full text-gray-400 text-sm">暂无数据</div>';
|
|
308
|
+
return;
|
|
309
|
+
}
|
|
310
|
+
|
|
311
|
+
const maxVal = Math.max(...days.map(d => Math.max(d.input || 0, d.output || 0)), 1);
|
|
312
|
+
|
|
313
|
+
days.forEach(day => {
|
|
314
|
+
const col = document.createElement('div');
|
|
315
|
+
col.className = 'flex-1 flex flex-col items-center justify-end space-y-1';
|
|
316
|
+
|
|
317
|
+
const inputH = Math.max(4, ((day.input || 0) / maxVal) * 140);
|
|
318
|
+
const outputH = Math.max(4, ((day.output || 0) / maxVal) * 140);
|
|
319
|
+
|
|
320
|
+
col.innerHTML = `
|
|
321
|
+
<div class="w-full flex justify-center space-x-1">
|
|
322
|
+
<div class="w-3 bg-claude-orange rounded-t transition-all" style="height:${inputH}px" title="输入: ${fmtNum(day.input)}"></div>
|
|
323
|
+
<div class="w-3 bg-blue-500 rounded-t transition-all" style="height:${outputH}px" title="输出: ${fmtNum(day.output)}"></div>
|
|
324
|
+
</div>
|
|
325
|
+
<span class="text-xs text-gray-400 mt-1">${day.date ? new Date(day.date).toLocaleDateString('zh-CN', {month:'numeric', day:'numeric'}) : ''}</span>
|
|
326
|
+
`;
|
|
327
|
+
chart.appendChild(col);
|
|
328
|
+
});
|
|
329
|
+
} catch (e) {
|
|
330
|
+
console.error('加载用量失败:', e);
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
// 加载 API 密钥列表
|
|
335
|
+
async function loadApiKeys() {
|
|
336
|
+
try {
|
|
337
|
+
const res = await fetch('/api/dashboard/api-keys', { credentials: 'same-origin' });
|
|
338
|
+
if (!res.ok) throw new Error('请求失败');
|
|
339
|
+
const data = await res.json();
|
|
340
|
+
const keys = data.data || data || [];
|
|
341
|
+
|
|
342
|
+
const tbody = document.getElementById('apiKeysList');
|
|
343
|
+
if (!keys.length) {
|
|
344
|
+
tbody.innerHTML = '<tr><td colspan="5" class="text-center py-8 text-gray-400">暂无 API 密钥,点击上方按钮创建</td></tr>';
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
tbody.innerHTML = keys.map(k => `
|
|
349
|
+
<tr class="border-b border-gray-50 hover:bg-gray-50/50">
|
|
350
|
+
<td class="py-3 px-2 font-medium text-claude-dark">${escHtml(k.name || '未命名')}</td>
|
|
351
|
+
<td class="py-3 px-2">
|
|
352
|
+
<code class="text-xs font-mono bg-gray-100 px-2 py-1 rounded">${escHtml(k.maskedKey || k.key || '--')}</code>
|
|
353
|
+
</td>
|
|
354
|
+
<td class="py-3 px-2 text-gray-500">${fmtDate(k.createdAt)}</td>
|
|
355
|
+
<td class="py-3 px-2 text-gray-500">${k.lastUsedAt ? fmtDate(k.lastUsedAt) : '从未'}</td>
|
|
356
|
+
<td class="py-3 px-2 text-right">
|
|
357
|
+
<button onclick="deleteApiKey('${k.id || k._id}')" class="text-xs text-red-500 hover:text-red-700 hover:underline transition-colors">删除</button>
|
|
358
|
+
</td>
|
|
359
|
+
</tr>
|
|
360
|
+
`).join('');
|
|
361
|
+
} catch (e) {
|
|
362
|
+
console.error('加载密钥失败:', e);
|
|
363
|
+
}
|
|
364
|
+
}
|
|
365
|
+
|
|
366
|
+
function escHtml(s) {
|
|
367
|
+
const d = document.createElement('div');
|
|
368
|
+
d.textContent = s;
|
|
369
|
+
return d.innerHTML;
|
|
370
|
+
}
|
|
371
|
+
|
|
372
|
+
// 创建 API 密钥
|
|
373
|
+
async function createApiKey() {
|
|
374
|
+
const nameInput = document.getElementById('newKeyName');
|
|
375
|
+
const name = nameInput.value.trim();
|
|
376
|
+
if (!name) {
|
|
377
|
+
nameInput.focus();
|
|
378
|
+
nameInput.classList.add('ring-2', 'ring-red-300');
|
|
379
|
+
setTimeout(() => nameInput.classList.remove('ring-2', 'ring-red-300'), 2000);
|
|
380
|
+
return;
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
try {
|
|
384
|
+
const res = await fetch('/api/dashboard/api-keys', {
|
|
385
|
+
method: 'POST',
|
|
386
|
+
headers: { 'Content-Type': 'application/json' },
|
|
387
|
+
credentials: 'same-origin',
|
|
388
|
+
body: JSON.stringify({ name })
|
|
389
|
+
});
|
|
390
|
+
if (!res.ok) {
|
|
391
|
+
const err = await res.json().catch(() => ({}));
|
|
392
|
+
throw new Error(err.message || '创建失败');
|
|
393
|
+
}
|
|
394
|
+
const data = await res.json();
|
|
395
|
+
const key = data.data || data;
|
|
396
|
+
|
|
397
|
+
// 显示新密钥
|
|
398
|
+
document.getElementById('newKeyValue').textContent = key.key || key.apiKey || '';
|
|
399
|
+
document.getElementById('newKeyDisplay').classList.remove('hidden');
|
|
400
|
+
nameInput.value = '';
|
|
401
|
+
|
|
402
|
+
// 刷新列表
|
|
403
|
+
loadApiKeys();
|
|
404
|
+
loadStats();
|
|
405
|
+
} catch (e) {
|
|
406
|
+
alert('创建密钥失败:' + e.message);
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// 删除 API 密钥
|
|
411
|
+
async function deleteApiKey(id) {
|
|
412
|
+
if (!confirm('确定要删除该密钥吗?删除后使用此密钥的服务将立即失效。')) return;
|
|
413
|
+
|
|
414
|
+
try {
|
|
415
|
+
const res = await fetch('/api/dashboard/api-keys/' + id, {
|
|
416
|
+
method: 'DELETE',
|
|
417
|
+
credentials: 'same-origin'
|
|
418
|
+
});
|
|
419
|
+
if (!res.ok) throw new Error('删除失败');
|
|
420
|
+
loadApiKeys();
|
|
421
|
+
loadStats();
|
|
422
|
+
} catch (e) {
|
|
423
|
+
alert('删除失败:' + e.message);
|
|
424
|
+
}
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
// 初始化加载
|
|
428
|
+
loadStats();
|
|
429
|
+
loadUsage();
|
|
430
|
+
loadApiKeys();
|
|
431
|
+
|
|
432
|
+
// 自动刷新 30s
|
|
433
|
+
setInterval(() => {
|
|
434
|
+
loadStats();
|
|
435
|
+
loadUsage();
|
|
436
|
+
loadApiKeys();
|
|
437
|
+
}, 30000);
|
|
438
|
+
</script>
|
|
439
|
+
|
|
440
|
+
</body>
|
|
441
|
+
</html>
|