asemxin commited on
Commit
731d915
·
1 Parent(s): ce37b8f

feat: add API key modification, account deletion, password protection

Browse files
Files changed (3) hide show
  1. account_manager.py +47 -0
  2. app.py +121 -12
  3. models.py +2 -0
account_manager.py CHANGED
@@ -11,6 +11,7 @@ from models import Account, OAuthToken, AccountStats
11
  # 数据文件路径 (HF Spaces 持久化目录)
12
  DATA_DIR = os.environ.get("DATA_DIR", "./data")
13
  ACCOUNTS_FILE = os.path.join(DATA_DIR, "accounts.json")
 
14
 
15
  # Google OAuth 配置 (Antigravity 使用的 Client ID)
16
  OAUTH_CLIENT_ID = "595848968694-r5ng3t6qb9elhe1u1h1hqgq4j2r3hgvk.apps.googleusercontent.com"
@@ -189,5 +190,51 @@ class AccountManager:
189
  )
190
 
191
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
192
  # 全局单例
193
  account_manager = AccountManager()
 
 
 
11
  # 数据文件路径 (HF Spaces 持久化目录)
12
  DATA_DIR = os.environ.get("DATA_DIR", "./data")
13
  ACCOUNTS_FILE = os.path.join(DATA_DIR, "accounts.json")
14
+ CONFIG_FILE = os.path.join(DATA_DIR, "config.json")
15
 
16
  # Google OAuth 配置 (Antigravity 使用的 Client ID)
17
  OAUTH_CLIENT_ID = "595848968694-r5ng3t6qb9elhe1u1h1hqgq4j2r3hgvk.apps.googleusercontent.com"
 
190
  )
191
 
192
 
193
+ class ConfigManager:
194
+ """配置管理器 - 管理 API Key 等可变配置"""
195
+
196
+ def __init__(self):
197
+ self._config = {
198
+ "api_key": "sk-antigravity"
199
+ }
200
+ self._ensure_data_dir()
201
+ self._load_config()
202
+
203
+ def _ensure_data_dir(self):
204
+ """确保数据目录存在"""
205
+ os.makedirs(DATA_DIR, exist_ok=True)
206
+
207
+ def _load_config(self):
208
+ """从文件加载配置"""
209
+ if os.path.exists(CONFIG_FILE):
210
+ try:
211
+ with open(CONFIG_FILE, "r", encoding="utf-8") as f:
212
+ self._config.update(json.load(f))
213
+ except Exception as e:
214
+ print(f"加载配置失败: {e}")
215
+
216
+ def _save_config(self):
217
+ """保存配置到文件"""
218
+ try:
219
+ with open(CONFIG_FILE, "w", encoding="utf-8") as f:
220
+ json.dump(self._config, f, ensure_ascii=False, indent=2)
221
+ except Exception as e:
222
+ print(f"保存配置失败: {e}")
223
+
224
+ def get_api_key(self) -> str:
225
+ """获取 API Key"""
226
+ return self._config.get("api_key", "sk-antigravity")
227
+
228
+ def set_api_key(self, api_key: str) -> bool:
229
+ """设置 API Key"""
230
+ if not api_key or len(api_key.strip()) == 0:
231
+ return False
232
+ self._config["api_key"] = api_key.strip()
233
+ self._save_config()
234
+ return True
235
+
236
+
237
  # 全局单例
238
  account_manager = AccountManager()
239
+ config_manager = ConfigManager()
240
+
app.py CHANGED
@@ -11,13 +11,17 @@ from fastapi.middleware.cors import CORSMiddleware
11
  from contextlib import asynccontextmanager
12
 
13
  from models import OpenAIChatRequest, OpenAIMessage, SUPPORTED_MODELS, ServiceConfig
14
- from account_manager import account_manager
15
  from load_balancer import load_balancer
16
  from proxy_service import stream_chat_completion, chat_completion
17
 
18
  # ============ 服务配置 ============
19
  config = ServiceConfig()
20
 
 
 
 
 
21
 
22
  # ============ FastAPI 应用 ============
23
  @asynccontextmanager
@@ -74,6 +78,12 @@ async def chat_completions(request: Request):
74
  if not auth_header.startswith("Bearer "):
75
  raise HTTPException(status_code=401, detail="Missing API key")
76
 
 
 
 
 
 
 
77
  # 获取请求体
78
  body = await request.json()
79
 
@@ -128,10 +138,11 @@ def get_accounts_table():
128
  """获取账号列表表格数据"""
129
  accounts = account_manager.get_all_accounts()
130
  if not accounts:
131
- return [["暂无账号", "-", "-", "-", "-"]]
132
 
133
  return [
134
  [
 
135
  acc.email,
136
  "✅ 正常" if acc.is_available() else "❌ 冷却中",
137
  str(acc.total_requests),
@@ -159,9 +170,54 @@ def add_account(email: str, access_token: str, refresh_token: str, project_id: s
159
  return f"❌ 添加失败: {e}", get_accounts_table()
160
 
161
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
162
  def get_service_info():
163
  """获取服务信息"""
164
  stats = account_manager.get_stats()
 
165
 
166
  # 获取当前 Space URL
167
  space_url = os.environ.get("SPACE_HOST", "localhost:7860")
@@ -173,7 +229,7 @@ def get_service_info():
173
 
174
  **Base URL:** `{space_url}/v1`
175
 
176
- **API Key:** `{config.api_key}` (或任意非空字符串)
177
 
178
  **支持的模型:**
179
  - `gemini-2.5-pro` / `gemini-2.5-flash` / `gemini-2.5-flash-lite`
@@ -199,7 +255,7 @@ def get_service_info():
199
  ```bash
200
  curl {space_url}/v1/chat/completions \\
201
  -H "Content-Type: application/json" \\
202
- -H "Authorization: Bearer sk-antigravity" \\
203
  -d '{{
204
  "model": "gemini-2.5-flash",
205
  "messages": [{{"role": "user", "content": "Hello"}}],
@@ -210,7 +266,7 @@ curl {space_url}/v1/chat/completions \\
210
  ### NextChat / Cherry Studio 配置
211
  1. API 类型: OpenAI
212
  2. Base URL: `{space_url}/v1`
213
- 3. API Key: `sk-antigravity`
214
  """
215
 
216
 
@@ -224,6 +280,7 @@ with gr.Blocks(
224
  css="""
225
  .container { max-width: 1200px; margin: auto; }
226
  .logo { font-size: 2em; font-weight: bold; }
 
227
  """
228
  ) as demo:
229
  gr.Markdown(
@@ -252,7 +309,7 @@ with gr.Blocks(
252
 
253
  access_token_input = gr.Textbox(
254
  label="Access Token",
255
- placeholder="ya29.xxx...",
256
  lines=2
257
  )
258
  refresh_token_input = gr.Textbox(
@@ -264,34 +321,86 @@ with gr.Blocks(
264
  add_btn = gr.Button("➕ 添加账号", variant="primary")
265
  result_text = gr.Textbox(label="结果", interactive=False)
266
 
 
267
  gr.Markdown("### 账号列表")
268
  accounts_table = gr.Dataframe(
269
- headers=["邮箱", "状态", "请求数", "成功率", "最后使用"],
270
  value=get_accounts_table,
271
  interactive=False
272
  )
273
 
 
 
 
 
 
 
 
 
 
 
 
274
  add_btn.click(
275
  fn=add_account,
276
  inputs=[email_input, access_token_input, refresh_token_input, project_id_input],
277
  outputs=[result_text, accounts_table]
 
 
 
 
 
 
 
 
 
 
 
 
278
  )
279
 
280
  # 设置
281
  with gr.Tab("⚙️ 设置"):
282
- gr.Markdown("### 服务配置")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
283
  gr.Markdown("""
284
- > 以下配置在 HF Spaces 环境下通过环境变量设置
285
 
286
  | 配置项 | 环境变量 | 默认值 |
287
  |--------|----------|--------|
288
  | 数据目录 | `DATA_DIR` | `./data` |
289
- | API Key | - | `sk-antigravity` |
 
290
  """)
291
 
292
 
293
- # 挂载 Gradio 到 FastAPI
294
- app = gr.mount_gradio_app(app, demo, path="/")
 
 
 
 
 
 
295
 
296
 
297
  if __name__ == "__main__":
 
11
  from contextlib import asynccontextmanager
12
 
13
  from models import OpenAIChatRequest, OpenAIMessage, SUPPORTED_MODELS, ServiceConfig
14
+ from account_manager import account_manager, config_manager
15
  from load_balancer import load_balancer
16
  from proxy_service import stream_chat_completion, chat_completion
17
 
18
  # ============ 服务配置 ============
19
  config = ServiceConfig()
20
 
21
+ # 从环境变量获取管理员凭据
22
+ ADMIN_USERNAME = os.environ.get("ADMIN_USERNAME", "admin")
23
+ ADMIN_PASSWORD = os.environ.get("ADMIN_PASSWORD", "antigravity")
24
+
25
 
26
  # ============ FastAPI 应用 ============
27
  @asynccontextmanager
 
78
  if not auth_header.startswith("Bearer "):
79
  raise HTTPException(status_code=401, detail="Missing API key")
80
 
81
+ # 验证 API Key 是否匹配
82
+ provided_key = auth_header[7:] # 去掉 "Bearer " 前缀
83
+ expected_key = config_manager.get_api_key()
84
+ if provided_key != expected_key:
85
+ raise HTTPException(status_code=401, detail="Invalid API key")
86
+
87
  # 获取请求体
88
  body = await request.json()
89
 
 
138
  """获取账号列表表格数据"""
139
  accounts = account_manager.get_all_accounts()
140
  if not accounts:
141
+ return [["暂无账号", "-", "-", "-", "-", "-"]]
142
 
143
  return [
144
  [
145
+ acc.id,
146
  acc.email,
147
  "✅ 正常" if acc.is_available() else "❌ 冷却中",
148
  str(acc.total_requests),
 
170
  return f"❌ 添加失败: {e}", get_accounts_table()
171
 
172
 
173
+ def delete_account(email_to_delete: str):
174
+ """删除账号"""
175
+ if not email_to_delete or email_to_delete == "暂无账号":
176
+ return "❌ 请选择要删除的账号", get_accounts_table()
177
+
178
+ # 查找账号 ID
179
+ accounts = account_manager.get_all_accounts()
180
+ target_account = None
181
+ for acc in accounts:
182
+ if acc.email == email_to_delete:
183
+ target_account = acc
184
+ break
185
+
186
+ if not target_account:
187
+ return f"❌ 账号 {email_to_delete} 不存在", get_accounts_table()
188
+
189
+ try:
190
+ if account_manager.remove_account(target_account.id):
191
+ return f"✅ 账号 {email_to_delete} 已删除", get_accounts_table()
192
+ else:
193
+ return f"❌ 删除账号 {email_to_delete} 失败", get_accounts_table()
194
+ except Exception as e:
195
+ return f"❌ 删除失败: {e}", get_accounts_table()
196
+
197
+
198
+ def get_account_emails():
199
+ """获取所有账号邮箱列表"""
200
+ accounts = account_manager.get_all_accounts()
201
+ if not accounts:
202
+ return ["暂无账号"]
203
+ return [acc.email for acc in accounts]
204
+
205
+
206
+ def update_api_key(new_api_key: str):
207
+ """更新 API Key"""
208
+ if not new_api_key or len(new_api_key.strip()) < 3:
209
+ return "❌ API Key 不能为空且至少需要 3 个字符", config_manager.get_api_key()
210
+
211
+ if config_manager.set_api_key(new_api_key.strip()):
212
+ return f"✅ API Key 已更新为: {new_api_key.strip()}", new_api_key.strip()
213
+ else:
214
+ return "❌ 更新 API Key 失败", config_manager.get_api_key()
215
+
216
+
217
  def get_service_info():
218
  """获取服务信息"""
219
  stats = account_manager.get_stats()
220
+ current_api_key = config_manager.get_api_key()
221
 
222
  # 获取当前 Space URL
223
  space_url = os.environ.get("SPACE_HOST", "localhost:7860")
 
229
 
230
  **Base URL:** `{space_url}/v1`
231
 
232
+ **API Key:** `{current_api_key}`
233
 
234
  **支持的模型:**
235
  - `gemini-2.5-pro` / `gemini-2.5-flash` / `gemini-2.5-flash-lite`
 
255
  ```bash
256
  curl {space_url}/v1/chat/completions \\
257
  -H "Content-Type: application/json" \\
258
+ -H "Authorization: Bearer {current_api_key}" \\
259
  -d '{{
260
  "model": "gemini-2.5-flash",
261
  "messages": [{{"role": "user", "content": "Hello"}}],
 
266
  ### NextChat / Cherry Studio 配置
267
  1. API 类型: OpenAI
268
  2. Base URL: `{space_url}/v1`
269
+ 3. API Key: `{current_api_key}`
270
  """
271
 
272
 
 
280
  css="""
281
  .container { max-width: 1200px; margin: auto; }
282
  .logo { font-size: 2em; font-weight: bold; }
283
+ .danger-btn { background-color: #dc3545 !important; }
284
  """
285
  ) as demo:
286
  gr.Markdown(
 
309
 
310
  access_token_input = gr.Textbox(
311
  label="Access Token",
312
+ placeholder="ya29.xxx... 或填 pending",
313
  lines=2
314
  )
315
  refresh_token_input = gr.Textbox(
 
321
  add_btn = gr.Button("➕ 添加账号", variant="primary")
322
  result_text = gr.Textbox(label="结果", interactive=False)
323
 
324
+ gr.Markdown("---")
325
  gr.Markdown("### 账号列表")
326
  accounts_table = gr.Dataframe(
327
+ headers=["ID", "邮箱", "状态", "请求数", "成功率", "最后使用"],
328
  value=get_accounts_table,
329
  interactive=False
330
  )
331
 
332
+ gr.Markdown("### 删除账号")
333
+ with gr.Row():
334
+ delete_email_dropdown = gr.Dropdown(
335
+ label="选择要删除的账号",
336
+ choices=get_account_emails,
337
+ interactive=True
338
+ )
339
+ delete_btn = gr.Button("🗑️ 删除账号", variant="stop")
340
+ delete_result = gr.Textbox(label="删除结果", interactive=False)
341
+
342
+ # 绑定事件
343
  add_btn.click(
344
  fn=add_account,
345
  inputs=[email_input, access_token_input, refresh_token_input, project_id_input],
346
  outputs=[result_text, accounts_table]
347
+ ).then(
348
+ fn=get_account_emails,
349
+ outputs=delete_email_dropdown
350
+ )
351
+
352
+ delete_btn.click(
353
+ fn=delete_account,
354
+ inputs=[delete_email_dropdown],
355
+ outputs=[delete_result, accounts_table]
356
+ ).then(
357
+ fn=get_account_emails,
358
+ outputs=delete_email_dropdown
359
  )
360
 
361
  # 设置
362
  with gr.Tab("⚙️ 设置"):
363
+ gr.Markdown("### API Key 配置")
364
+ gr.Markdown("> 修改后,所有 API 请求需要使用新的 API Key")
365
+
366
+ with gr.Row():
367
+ api_key_input = gr.Textbox(
368
+ label="当前 API Key",
369
+ value=config_manager.get_api_key,
370
+ placeholder="输入新的 API Key",
371
+ interactive=True
372
+ )
373
+ save_api_key_btn = gr.Button("💾 保存 API Key", variant="primary")
374
+
375
+ api_key_result = gr.Textbox(label="结果", interactive=False)
376
+
377
+ save_api_key_btn.click(
378
+ fn=update_api_key,
379
+ inputs=[api_key_input],
380
+ outputs=[api_key_result, api_key_input]
381
+ )
382
+
383
+ gr.Markdown("---")
384
+ gr.Markdown("### 环境变量配置")
385
  gr.Markdown("""
386
+ > 以下配置在 HF Spaces 环境下通过 Secrets 设置
387
 
388
  | 配置项 | 环境变量 | 默认值 |
389
  |--------|----------|--------|
390
  | 数据目录 | `DATA_DIR` | `./data` |
391
+ | 管理员用户名 | `ADMIN_USERNAME` | `admin` |
392
+ | 管理员密码 | `ADMIN_PASSWORD` | `antigravity` |
393
  """)
394
 
395
 
396
+ # 挂载 Gradio 到 FastAPI (带密码保护)
397
+ app = gr.mount_gradio_app(
398
+ app,
399
+ demo,
400
+ path="/",
401
+ auth=(ADMIN_USERNAME, ADMIN_PASSWORD),
402
+ auth_message="🔐 请输入管理员凭据登录 Antigravity API Proxy"
403
+ )
404
 
405
 
406
  if __name__ == "__main__":
models.py CHANGED
@@ -64,6 +64,8 @@ class AccountStats(BaseModel):
64
  class ServiceConfig(BaseModel):
65
  """服务配置"""
66
  api_key: str = "sk-antigravity"
 
 
67
  port: int = 7860
68
  enable_cors: bool = True
69
  max_retries: int = 3
 
64
  class ServiceConfig(BaseModel):
65
  """服务配置"""
66
  api_key: str = "sk-antigravity"
67
+ admin_username: str = "admin"
68
+ admin_password: str = "antigravity"
69
  port: int = 7860
70
  enable_cors: bool = True
71
  max_retries: int = 3