max1949 commited on
Commit
da7caff
·
verified ·
1 Parent(s): d090097

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +220 -197
app.py CHANGED
@@ -20,67 +20,60 @@ from PIL import Image, ImageDraw, ImageFont
20
  import gradio as gr
21
 
22
  # ==============================================================================
23
- # 0. 机构级审计日志 (Institutional Logging)
24
  # ==============================================================================
25
  logging.basicConfig(
26
  level=logging.INFO,
27
  format='%(asctime)s [%(levelname)s] %(message)s',
28
  handlers=[logging.StreamHandler(sys.stdout)]
29
  )
30
- logger = logging.getLogger("BK_GTA_ARCHITECT_GRADIO")
31
 
32
  # ==============================================================================
33
- # 1. 物理层适配:Gradio 哨兵 (Sentinel Probe)
34
  # ==============================================================================
35
  def get_status():
36
  """
37
- 提供给 UptimeRobot 监控探针内容
38
  """
39
  now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
 
40
  latency = "N/A"
41
  try:
42
  if bot and bot.is_ready():
 
43
  latency = f"{round(bot.latency * 1000, 2)}ms"
44
- except:
45
- pass
46
 
47
  return {
48
- "status": "BK-GTA-GRADIO-TITAN-ACTIVE",
49
- "version": "V62.9-Stable",
50
- "bot_latency": latency,
 
 
51
  "server_time": now,
52
- "keys_loaded": len(KEY_POOL),
53
- "memory_usage": f"{sys.getsizeof(bk_engine.data) / 1024:.2f} KB"
54
  }
55
 
56
  # ==============================================================================
57
- # 2. 密钥池与环境配置 (Key Rotation Engine)
58
  # ==============================================================================
59
- DISCORD_TOKEN = os.environ.get('DISCORD_TOKEN')
60
  SESSION_ID = random.randint(10000, 99999)
61
 
62
  # [CORE] 8 密钥轮询加载器
63
  raw_keys = os.environ.get('GEMINI_KEYS_POOL', '')
64
- KEY_POOL = []
65
-
66
- if raw_keys:
67
- parts = raw_keys.split(',')
68
- for k in parts:
69
- clean_key = k.strip()
70
- if clean_key:
71
- KEY_POOL.append(clean_key)
72
- logger.info(f"✅ [System] Loaded GEMINI_KEYS_POOL with {len(KEY_POOL)} keys.")
73
- else:
74
- # 兼容单密钥模式
75
  single = os.environ.get('GEMINI_KEY')
76
- if single:
77
- KEY_POOL.append(single)
78
- logger.warning("⚠️ [System] Running in SINGLE KEY mode.")
79
 
80
  if not KEY_POOL:
81
- logger.critical("❌ [CRITICAL] NO API KEYS FOUND! Please set GEMINI_KEYS_POOL in Secrets.")
82
 
83
- # 设置 Discord 权限
84
  intents = discord.Intents.default()
85
  intents.message_content = True
86
  intents.members = True
@@ -88,21 +81,18 @@ bot = commands.Bot(command_prefix='!', intents=intents)
88
 
89
  # 并发审计锁
90
  audit_lock = asyncio.Lock()
91
-
92
- # 数据库路径适配
93
- if os.path.exists('/data'):
94
- DB_PATH_CONFIG = '/data/bk_gta_v62_unified.json'
95
- logger.info(f"📂 [Storage] Using Persistent Storage: {DB_PATH_CONFIG}")
96
- else:
97
- DB_PATH_CONFIG = 'bk_gta_v62_unified.json'
98
- logger.warning(f"⚠️ [Storage] Using Ephemeral Storage.")
99
 
100
  # ==============================================================================
101
  # 3. 状态引擎 (BKEngine Core)
102
  # ==============================================================================
103
  class BKEngine:
 
 
 
104
  def __init__(self):
105
- self.db_file = DB_PATH_CONFIG
106
  self.data = self._load_db()
107
  self.pending_challenges = {}
108
  self.active_duels = {}
@@ -122,15 +112,15 @@ class BKEngine:
122
 
123
  def _init_schema(self):
124
  return {
125
- "version": "62.9-Gradio",
126
  "users": {},
127
  "factions": {
128
- "BULLS": {"score": 0, "emoji": "🐂", "total_wins": 0},
129
- "BEARS": {"score": 0, "emoji": "🐻", "total_wins": 0}
130
  },
131
  "prize_pool": 1000000.0,
132
  "logs": [],
133
- "last_global_sync": str(datetime.datetime.now())
134
  }
135
 
136
  def save_db(self):
@@ -140,33 +130,31 @@ class BKEngine:
140
  json.dump(self.data, f, ensure_ascii=False, indent=4)
141
  os.replace(temp, self.db_file)
142
  except Exception as e:
143
- logger.error(f"💾 [DB Error] Failed to persist data: {e}")
144
 
145
  def get_user(self, user_id):
146
  uid = str(user_id)
147
  if uid not in self.data["users"]:
148
  self.data["users"][uid] = {
149
- "elo": 1200,
150
- "points": 1000,
151
- "faction": None,
152
- "audits_count": 0,
153
- "total_score": 0.0,
154
- "highest_score": 0.0,
155
- "win_streak": 0,
156
- "mdd_record": 0.0,
157
- "sharpe_record": 0.0,
158
- "last_active": str(datetime.datetime.now())
159
  }
160
  return self.data["users"][uid]
161
 
162
- def update_audit_stats(self, user_id, score, mdd=None, sharpe=None):
163
  u = self.get_user(user_id)
164
  u["audits_count"] += 1
165
  u["total_score"] += score
166
- if score > u.get("highest_score", 0): u["highest_score"] = score
 
167
  if mdd is not None: u["mdd_record"] = mdd
168
  if sharpe is not None: u["sharpe_record"] = sharpe
169
  u["points"] += 50
 
 
 
170
  u["last_active"] = str(datetime.datetime.now())
171
  self.save_db()
172
 
@@ -175,20 +163,24 @@ class BKEngine:
175
  K = 32
176
  Rw, Rl = w["elo"], l["elo"]
177
  Ew = 1 / (1 + 10 ** ((Rl - Rw) / 400))
 
178
  w["elo"] = round(Rw + K * (1 - Ew))
179
  l["elo"] = round(Rl + K * (0 - Ew))
 
180
  w["win_streak"] += 1
181
  l["win_streak"] = 0
182
- if w["faction"]:
 
183
  self.data["factions"][w["faction"]]["score"] += 15
184
- self.data["factions"][w["faction"]]["total_wins"] += 1
 
185
  self.save_db()
186
  return w["elo"], l["elo"], w["win_streak"]
187
 
188
  bk_engine = BKEngine()
189
 
190
  # ==============================================================================
191
- # 4. 辅助内核 (Helper Kernels)
192
  # ==============================================================================
193
  def safe_extract_float(text, key):
194
  try:
@@ -198,78 +190,80 @@ def safe_extract_float(text, key):
198
  rf"【{key}】\s*([-+]?\d*\.?\d+)"
199
  ]
200
  for p in patterns:
201
- match = re.search(p, text, re.IGNORECASE)
202
  if match: return float(match.group(1))
203
  return 0.0
204
  except: return 0.0
205
 
206
- def safe_extract_text(text, key_start, key_end=None):
207
  try:
208
- p = rf"{key_start}:(.*?){key_end}:" if key_end else rf"{key_start}:(.*)"
209
- match = re.search(p, text, re.DOTALL | re.IGNORECASE)
210
  if match:
211
  return match.group(1).strip().replace('*', '').replace('`', '').strip()
212
  return "N/A"
213
  except: return "N/A"
214
 
215
- async def call_gemini_multiplex(prompt, attachment=None):
216
- if not KEY_POOL: return "ERROR: NO_KEYS_CONFIGURED"
 
 
 
217
 
218
- image_data = None
219
  if attachment:
220
- try: image_data = await attachment.read()
221
- except: return "ERROR: IMG_READ_FAIL"
222
 
223
  loop = asyncio.get_running_loop()
224
- shuffled_keys = list(KEY_POOL)
225
- random.shuffle(shuffled_keys)
226
 
227
- for i, api_key in enumerate(shuffled_keys):
228
  try:
229
  client = genai.Client(api_key=api_key)
230
  contents = [prompt]
231
- if image_data:
232
- contents.append(types.Part.from_bytes(data=image_data, mime_type='image/png'))
233
 
234
  response = await loop.run_in_executor(
235
  None,
236
  functools.partial(client.models.generate_content, model="gemini-1.5-flash", contents=contents)
237
  )
238
-
239
- if response and response.text: return response.text
240
-
241
  except Exception as e:
242
- err_msg = str(e)
243
- if "429" in err_msg:
244
- logger.warning(f"⚠️ Key {i+1} Rate Limited. Switching...")
245
  continue
246
- logger.error(f"🤖 Key {i+1} Error: {err_msg}")
247
  continue
248
 
249
  return "ERROR: ALL_KEYS_EXHAUSTED"
250
 
251
  # ==============================================================================
252
- # 5. 模块一:全网天梯
253
  # ==============================================================================
254
  @bot.command()
255
  async def profile(ctx, member: discord.Member = None):
256
- target = member or ctx.author
257
- u = bk_engine.get_user(target.id)
258
  elo = u['elo']
259
 
260
- if elo < 1300: rank, color = "Iron (学徒) 🗑️", 0x717d7e
261
- elif elo < 1500: rank, color = "Bronze (资深) 🥉", 0xcd7f32
262
- elif elo < 1800: rank, color = "Silver (精英) 🥈", 0xbdc3c7
263
- elif elo < 2100: rank, color = "Gold (专家) 🥇", 0xf1c40f
264
- elif elo < 2500: rank, color = "Platinum (大师) 💎", 0x3498db
265
- else: rank, color = "Grandmaster (传奇) 👑", 0x9b59b6
266
-
267
- emb = discord.Embed(title=f"📊 Quantitative Profile: {target.display_name}", color=color)
268
- emb.add_field(name="Rating (ELO)", value=f"**{elo}**\nRank: `{rank}`", inline=True)
269
- emb.add_field(name="Assets (Points)", value=f"💰 **{u['points']}**", inline=True)
 
270
 
271
  f_info = u['faction'] or "Free Agent"
272
- f_emoji = bk_engine.data["factions"].get(f_info, {}).get("emoji", "")
273
  emb.add_field(name="Faction", value=f"{f_emoji} {f_info}", inline=True)
274
 
275
  stats = (
@@ -277,147 +271,165 @@ async def profile(ctx, member: discord.Member = None):
277
  f"Peak Score: {u['highest_score']:.1f}\n"
278
  f"Max MDD: {u.get('mdd_record',0):.2f}%"
279
  )
280
- emb.add_field(name="Performance Metrics", value=f"```yaml\n{stats}\n```", inline=False)
281
 
282
- if target.avatar: emb.set_thumbnail(url=target.avatar.url)
283
- emb.set_footer(text=f"Session: {SESSION_ID} | Node: HuggingFace")
 
284
  await ctx.send(embed=emb)
285
 
286
  @bot.command()
287
  async def leaderboard(ctx):
288
- users = [{"id": k, "elo": v["elo"], "score": v["highest_score"]} for k,v in bk_engine.data["users"].items()]
289
- top = sorted(users, key=lambda x: x["elo"], reverse=True)[:10]
 
290
 
 
291
  desc = ""
292
- for i, u in enumerate(top):
293
- icon = ["🥇","🥈","🥉"][i] if i < 3 else f"{i+1}."
294
  desc += f"{icon} <@{u['id']}> : **{u['elo']}** (Peak: {u['score']:.1f})\n"
295
 
296
- emb = discord.Embed(title="🏆 Global Ranking (Institutional)", description=desc or "No data available.", color=0xf1c40f)
297
 
298
- f_txt = ""
299
  for k, v in bk_engine.data["factions"].items():
300
- f_txt += f"{v['emoji']} {k}: {v['score']} pts | "
301
- if f_txt: emb.add_field(name="Faction War Status", value=f_txt, inline=False)
302
-
 
303
  await ctx.send(embed=emb)
304
 
305
  # ==============================================================================
306
- # 6. 模块二:宏观情报
307
  # ==============================================================================
308
  @bot.command()
309
  async def news(ctx):
310
- if not ctx.message.attachments: return await ctx.send("❌ Audit Refused: Please upload an image.")
311
- m = await ctx.send("📡 **[HF-Node] Analyzing Macro Pulse...**")
312
 
313
  prompt = (
314
- "Analyze chart/news. Output: SCORE: (-10 to 10), SUMMARY: (text), TAKE: (institutional view). "
315
- "Format: SCORE: [v] SUMMARY: [v] TAKE: [v]"
 
 
 
316
  )
317
- res = await call_gemini_multiplex(prompt, ctx.message.attachments[0])
318
 
319
  score = safe_extract_float(res, "SCORE")
320
  summary = safe_extract_text(res, "SUMMARY", "TAKE")
321
  take = safe_extract_text(res, "TAKE")
322
 
323
- emb = discord.Embed(title="📰 Macro Intelligence", color=0x2ecc71 if score > 0 else 0xe74c3c)
324
- emb.add_field(name="Sentiment Score", value=f"**{score:+.1f}**")
325
- emb.add_field(name="Executive Summary", value=summary, inline=False)
326
- emb.add_field(name="Institutional Take", value=f"```{take}```", inline=False)
 
327
 
328
  await m.delete()
329
  await ctx.send(embed=emb)
330
 
331
  # ==============================================================================
332
- # 7. 模块三:量化审计
333
  # ==============================================================================
334
  @bot.command()
335
  async def evaluate(ctx):
336
- if not ctx.message.attachments: return await ctx.send("❌ No image detected.")
 
337
  if audit_lock.locked():
338
- return await ctx.send("⚠️ **[System Busy]** Another audit is processing. Please wait 10s.")
339
 
340
  async with audit_lock:
341
- m = await ctx.send("🛡️ **[Audit Engine] Processing... (Locked)**")
342
  prompt = (
343
- "Quant Audit. Extract RET, MDD, PF, SHARPE, TRADES. S_BASE(0-100). "
344
- "VIOLATION(TRUE if MDD>35). Highlights(H_ZH). "
345
- "Format: RET:[v] MDD:[v] PF:[v] SHARPE:[v] TRADES:[v] S_BASE:[v] VIOLATION:[v] H_ZH:[v]"
 
 
346
  )
347
 
348
- res = await call_gemini_multiplex(prompt, ctx.message.attachments[0])
349
 
350
  if "ERROR" in res:
351
  await m.delete()
352
- return await ctx.send(f"🔴 Audit Failed: {res}")
353
 
354
  ret = safe_extract_float(res, "RET")
355
  mdd = safe_extract_float(res, "MDD")
356
- pf = safe_extract_float(res, "PF")
357
  sharpe = safe_extract_float(res, "SHARPE")
358
  trades = int(safe_extract_float(res, "TRADES"))
359
  s_base = safe_extract_float(res, "S_BASE")
360
  violation = "TRUE" in res.upper()
361
 
 
362
  if violation or mdd > 35.0:
363
  s_final = 0.0
364
- status = "REJECTED (High Risk)"
365
  color = 0xe74c3c
366
  else:
367
- trades_adj = math.log10(max(trades, 1)) / 1.5
368
- s_final = s_base * min(max(trades_adj, 0.5), 1.2) * (sharpe/2.0 if sharpe>0 else 0.5)
369
- s_final = min(max(s_final, 0.0), 100.0)
370
- status = "CLEARED"
 
371
  color = 0x2ecc71
372
 
373
- bk_engine.update_audit_stats(ctx.author.id, s_final, mdd, sharpe)
 
374
 
375
- emb = discord.Embed(title="🛡️ Audit Report", color=color)
376
- emb.add_field(name="Verdict", value=status, inline=False)
377
- emb.add_field(name="Metrics", value=f"```Return: {ret}%\nMDD: {mdd}%\nSharpe: {sharpe}\nPF: {pf}```", inline=False)
378
- emb.add_field(name="Score", value=f"**{s_final:.1f}**")
379
- emb.add_field(name="Highlights", value=safe_extract_text(res, "H_ZH"), inline=False)
 
380
 
381
  await m.delete()
382
  await ctx.send(embed=emb)
383
 
384
  # ==============================================================================
385
- # 8. 模块四:博弈盘口
386
  # ==============================================================================
387
  @bot.command()
388
  @commands.has_permissions(administrator=True)
389
  async def open_bet(ctx, *, topic):
390
  bid = str(random.randint(100, 999))
391
  bk_engine.active_bets[bid] = {
392
- "topic": topic,
393
- "up": 0, "down": 0,
394
- "u_up": {}, "u_down": {},
395
- "status": "OPEN",
396
- "start_time": str(datetime.datetime.now())
397
  }
398
- await ctx.send(f"🎰 **Bet Market Open [{bid}]**\nTopic: {topic}\nType `!bet {bid} UP 100` to join.")
399
 
400
  @bot.command()
401
  async def bet(ctx, bid: str, side: str, amt: int):
402
- if bid not in bk_engine.active_bets or bk_engine.active_bets[bid]["status"] != "OPEN": return
 
 
403
  u = bk_engine.get_user(ctx.author.id)
404
- if u["points"] < amt: return await ctx.send("❌ Insufficient funds.")
 
405
 
406
- u["points"] -= amt; side = side.upper()
407
- if side not in ["UP", "DOWN"]: return await ctx.send("❌ Choose UP or DOWN.")
408
 
409
- pool = "u_up" if side == "UP" else "u_down"
410
  b = bk_engine.active_bets[bid]
 
411
  b[side.lower()] += amt
412
  b[pool][str(ctx.author.id)] = b[pool].get(str(ctx.author.id), 0) + amt
 
413
  bk_engine.save_db()
414
- await ctx.send(f"✅ Bet {amt} on {side} confirmed.")
415
 
416
  @bot.command()
417
  @commands.has_permissions(administrator=True)
418
  async def settle_bet(ctx, bid: str, winner: str):
419
  if bid not in bk_engine.active_bets: return
420
- b = bk_engine.active_bets[bid]; winner = winner.upper()
 
421
 
422
  win_pool = b["u_up"] if winner == "UP" else b["u_down"]
423
  total_w = b["up"] if winner == "UP" else b["down"]
@@ -425,29 +437,35 @@ async def settle_bet(ctx, bid: str, winner: str):
425
 
426
  if total_w > 0:
427
  for uid, amt in win_pool.items():
428
- profit = (amt/total_w) * total_l
429
  bk_engine.get_user(int(uid))["points"] += int(amt + profit)
430
 
431
  b["status"] = "SETTLED"
432
  bk_engine.save_db()
433
- await ctx.send(f"🏁 Bet {bid} Settled. Winner: {winner}. Payouts Distributed.")
434
 
435
  # ==============================================================================
436
- # 9. 模块五:PVP 竞技场
437
  # ==============================================================================
438
  @bot.command()
439
  async def challenge(ctx, member: discord.Member):
440
  if member.id == ctx.author.id: return
441
- did = f"D-{random.randint(1000,9999)}"
442
- bk_engine.pending_challenges[member.id] = {"c": ctx.author.id, "id": did}
443
- await ctx.send(f"⚔️ {ctx.author.name} challenges {member.name}!\nID: `{did}`. Type `!accept` to start.")
444
 
445
  @bot.command()
446
  async def accept(ctx):
447
- if ctx.author.id not in bk_engine.pending_challenges: return
 
 
448
  req = bk_engine.pending_challenges.pop(ctx.author.id)
449
- bk_engine.active_duels[req["id"]] = {"p1": req["c"], "p2": ctx.author.id, "s1": None, "s2": None}
450
- await ctx.send(f"🔥 Duel `{req['id']}` Active! Submit screenshots via `!duel_submit {req['id']}`")
 
 
 
 
451
 
452
  @bot.command()
453
  async def duel_submit(ctx, did: str):
@@ -457,8 +475,8 @@ async def duel_submit(ctx, did: str):
457
  duel = bk_engine.active_duels[did]
458
  if ctx.author.id not in [duel["p1"], duel["p2"]]: return
459
 
460
- m = await ctx.send("🔍 Auditing duel submission...")
461
- res = await call_gemini_multiplex("Extract SCORE:[0-100]. Format: SCORE:[v]", ctx.message.attachments[0])
462
  score = safe_extract_float(res, "SCORE")
463
 
464
  if ctx.author.id == duel["p1"]: duel["s1"] = score
@@ -468,97 +486,102 @@ async def duel_submit(ctx, did: str):
468
  p1, p2 = duel["p1"], duel["p2"]
469
  s1, s2 = duel["s1"], duel["s2"]
470
  wid, lid = (p1, p2) if s1 > s2 else (p2, p1)
471
- w_elo, l_elo, _ = bk_engine.update_elo(wid, lid)
472
 
473
- emb = discord.Embed(title="🏆 Duel Result", color=0xf1c40f)
474
- emb.add_field(name="Winner", value=f"<@{wid}>\nScore: {max(s1,s2)}\nELO: {w_elo}")
475
- emb.add_field(name="Loser", value=f"<@{lid}>\nScore: {min(s1,s2)}\nELO: {l_elo}")
 
476
  await ctx.send(embed=emb)
477
  del bk_engine.active_duels[did]
478
  else:
479
- await ctx.send(f"✅ Score {score} recorded. Waiting for opponent.")
480
  await m.delete()
481
 
482
  # ==============================================================================
483
- # 10. 模块六:海报渲染
484
  # ==============================================================================
485
  @bot.command()
486
  async def viral_audit(ctx):
487
  """
488
- [完版] 提取审计数据并渲染具备社交传播属性的海报图片
489
  """
490
- if not ctx.message.attachments:
491
- return await ctx.send(" 请上传审计截图。")
492
-
493
- m = await ctx.send("🎨 **[GPU-Node] Rendering Institutional Poster...**")
494
 
495
- prompt = "Extract RET: [val] and SHARPE: [val]. FORMAT: RET: [v] SHARPE: [v]"
496
- res = await call_gemini_multiplex(prompt, ctx.message.attachments[0])
497
  ret, sharpe = safe_extract_float(res, "RET"), safe_extract_float(res, "SHARPE")
498
 
499
  try:
500
  W, H = 600, 800
501
- base = Image.new('RGB', (W, H), color=(18, 20, 30))
 
502
  draw = ImageDraw.Draw(base)
503
 
 
504
  for i in range(0, W, 40): draw.line([(i, 0), (i, H)], fill=(30, 35, 45), width=1)
505
  for j in range(0, H, 40): draw.line([(0, j), (W, j)], fill=(30, 35, 45), width=1)
506
 
 
507
  draw.rectangle([10, 10, 590, 790], outline=(46, 204, 113), width=2)
508
  draw.rectangle([20, 20, 580, 780], outline=(46, 204, 113), width=5)
509
 
 
510
  draw.text((40, 60), "BK-GTA QUANTITATIVE SYSTEM", fill=(255, 255, 255))
511
- draw.text((40, 150), f"TRADER ID: {ctx.author.name.upper()}", fill=(46, 204, 113))
512
- draw.text((40, 260), f"ALPHA RETURN: +{ret}%", fill=(255, 255, 255))
513
- draw.text((40, 360), f"SHARPE RATIO: {sharpe}", fill=(255, 255, 255))
514
- draw.text((40, 460), f"SESSION: {SESSION_ID}", fill=(120, 120, 130))
515
-
516
- draw.text((40, 720), "VERIFIED BY BK-GTA SENTINEL AI", fill=(46, 204, 113))
517
 
518
  fp = io.BytesIO()
519
  base.save(fp, 'PNG')
520
  fp.seek(0)
521
  await ctx.send(file=discord.File(fp, filename=f"Viral_{ctx.author.id}.png"))
522
-
523
  except Exception as e:
524
  logger.error(f"Render Error: {e}")
525
- await ctx.send(f"❌ 渲染引擎报错: `{e}`")
526
  finally:
527
  await m.delete()
528
 
529
  # ==============================================================================
530
- # 11. 引擎 (Gradio + Discord Thread)
531
  # ==============================================================================
532
  @tasks.loop(hours=1)
533
  async def auto_backup():
534
  bk_engine.save_db()
 
535
 
536
  @bot.event
537
  async def on_ready():
538
  if not auto_backup.is_running(): auto_backup.start()
539
- await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name="HF-Native Node"))
540
- logger.info(f"✅ [V62.9-Gradio] Online. Session: {SESSION_ID}")
541
 
542
- def run_discord_bot():
543
- if DISCORD_TOKEN:
 
 
 
 
 
544
  try:
545
  bot.run(DISCORD_TOKEN)
546
  except Exception as e:
547
- logger.critical(f"Bot Crash: {e}")
548
- else:
549
- logger.critical("No Token")
550
 
551
  if __name__ == "__main__":
552
- # 1. 在后台线程启动 Discord 机器人 (非阻塞)
553
- threading.Thread(target=run_discord_bot).start()
554
 
555
- # 2. 在主线程启动 Gradio UI
556
- # [FIX] 移除了 every=5 参数以修复 TypeError
557
  iface = gr.Interface(
558
  fn=get_status,
559
  inputs=None,
560
  outputs="json",
561
- title="BK-GTA Sentinel",
562
- description="Core logic is running in background thread."
563
  )
564
  iface.launch(server_name="0.0.0.0", server_port=7860)
 
20
  import gradio as gr
21
 
22
  # ==============================================================================
23
+ # 0. 机构级审计日志 (Institutional Log Engine)
24
  # ==============================================================================
25
  logging.basicConfig(
26
  level=logging.INFO,
27
  format='%(asctime)s [%(levelname)s] %(message)s',
28
  handlers=[logging.StreamHandler(sys.stdout)]
29
  )
30
+ logger = logging.getLogger("BK_GTA_ARCHITECT_TITAN")
31
 
32
  # ==============================================================================
33
+ # 1. 物理层:Gradio 哨兵与探针 (Gradio Sentinel)
34
  # ==============================================================================
35
  def get_status():
36
  """
37
+ 提供给外部(如 UptimeRobot/Cron-Job)心跳对账数据
38
  """
39
  now = datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%S")
40
+ bot_status = "OFFLINE"
41
  latency = "N/A"
42
  try:
43
  if bot and bot.is_ready():
44
+ bot_status = "ONLINE"
45
  latency = f"{round(bot.latency * 1000, 2)}ms"
46
+ except Exception as e:
47
+ logger.error(f"Status check failed: {e}")
48
 
49
  return {
50
+ "status": "BK-GTA-TITAN-ACTIVE",
51
+ "logic_integrity": "100%",
52
+ "engine_version": "V62.9-Gradio-Final",
53
+ "discord_bot": bot_status,
54
+ "latency": latency,
55
  "server_time": now,
56
+ "keys_pool": len(KEY_POOL),
57
+ "db_size": f"{sys.getsizeof(bk_engine.data) / 1024:.2f} KB"
58
  }
59
 
60
  # ==============================================================================
61
+ # 2. 密钥池与环境配置 (Key Rotation & Env)
62
  # ==============================================================================
63
+ DISCORD_TOKEN = os.environ.get('DISCORD_TOKEN', '').strip()
64
  SESSION_ID = random.randint(10000, 99999)
65
 
66
  # [CORE] 8 密钥轮询加载器
67
  raw_keys = os.environ.get('GEMINI_KEYS_POOL', '')
68
+ KEY_POOL = [k.strip() for k in raw_keys.split(',') if k.strip()]
69
+ if not KEY_POOL:
 
 
 
 
 
 
 
 
 
70
  single = os.environ.get('GEMINI_KEY')
71
+ if single: KEY_POOL.append(single)
 
 
72
 
73
  if not KEY_POOL:
74
+ logger.critical("❌ [CRITICAL] NO API KEYS FOUND! Please set GEMINI_KEYS_POOL.")
75
 
76
+ # 权限位设置
77
  intents = discord.Intents.default()
78
  intents.message_content = True
79
  intents.members = True
 
81
 
82
  # 并发审计锁
83
  audit_lock = asyncio.Lock()
84
+ # 优先使用持久化目录 /data
85
+ DB_PATH = '/data/bk_gta_v62_unified.json' if os.path.exists('/data') else 'bk_gta_v62_unified.json'
 
 
 
 
 
 
86
 
87
  # ==============================================================================
88
  # 3. 状态引擎 (BKEngine Core)
89
  # ==============================================================================
90
  class BKEngine:
91
+ """
92
+ 负责量化资产的持久化、ELO 计算及风险对账
93
+ """
94
  def __init__(self):
95
+ self.db_file = DB_PATH
96
  self.data = self._load_db()
97
  self.pending_challenges = {}
98
  self.active_duels = {}
 
112
 
113
  def _init_schema(self):
114
  return {
115
+ "version": "62.9-Titan",
116
  "users": {},
117
  "factions": {
118
+ "BULLS": {"score": 0, "emoji": "🐂", "wins": 0},
119
+ "BEARS": {"score": 0, "emoji": "🐻", "wins": 0}
120
  },
121
  "prize_pool": 1000000.0,
122
  "logs": [],
123
+ "last_sync": str(datetime.datetime.now())
124
  }
125
 
126
  def save_db(self):
 
130
  json.dump(self.data, f, ensure_ascii=False, indent=4)
131
  os.replace(temp, self.db_file)
132
  except Exception as e:
133
+ logger.error(f"💾 [DB Error] {e}")
134
 
135
  def get_user(self, user_id):
136
  uid = str(user_id)
137
  if uid not in self.data["users"]:
138
  self.data["users"][uid] = {
139
+ "elo": 1200, "points": 1000, "faction": None,
140
+ "audits_count": 0, "total_score": 0.0, "highest_score": 0.0,
141
+ "win_streak": 0, "mdd_record": 0.0, "sharpe_record": 0.0,
142
+ "audit_history": [], "last_active": str(datetime.datetime.now())
 
 
 
 
 
 
143
  }
144
  return self.data["users"][uid]
145
 
146
+ def update_audit(self, user_id, score, mdd=None, sharpe=None):
147
  u = self.get_user(user_id)
148
  u["audits_count"] += 1
149
  u["total_score"] += score
150
+ if score > u.get("highest_score", 0):
151
+ u["highest_score"] = score
152
  if mdd is not None: u["mdd_record"] = mdd
153
  if sharpe is not None: u["sharpe_record"] = sharpe
154
  u["points"] += 50
155
+ # 记录最近 5 次审计历史
156
+ u["audit_history"].append({"s": score, "t": str(datetime.datetime.now())})
157
+ u["audit_history"] = u["audit_history"][-5:]
158
  u["last_active"] = str(datetime.datetime.now())
159
  self.save_db()
160
 
 
163
  K = 32
164
  Rw, Rl = w["elo"], l["elo"]
165
  Ew = 1 / (1 + 10 ** ((Rl - Rw) / 400))
166
+
167
  w["elo"] = round(Rw + K * (1 - Ew))
168
  l["elo"] = round(Rl + K * (0 - Ew))
169
+
170
  w["win_streak"] += 1
171
  l["win_streak"] = 0
172
+
173
+ if w["faction"] and w["faction"] in self.data["factions"]:
174
  self.data["factions"][w["faction"]]["score"] += 15
175
+ self.data["factions"][w["faction"]]["wins"] += 1
176
+
177
  self.save_db()
178
  return w["elo"], l["elo"], w["win_streak"]
179
 
180
  bk_engine = BKEngine()
181
 
182
  # ==============================================================================
183
+ # 4. 辅助内核与密钥多路复用 (Multiplexing Kernel)
184
  # ==============================================================================
185
  def safe_extract_float(text, key):
186
  try:
 
190
  rf"【{key}】\s*([-+]?\d*\.?\d+)"
191
  ]
192
  for p in patterns:
193
+ match = re.search(p, text, re.I)
194
  if match: return float(match.group(1))
195
  return 0.0
196
  except: return 0.0
197
 
198
+ def safe_extract_text(text, start, end=None):
199
  try:
200
+ p = rf"{start}:(.*?){end}:" if end else rf"{start}:(.*)"
201
+ match = re.search(p, text, re.S | re.I)
202
  if match:
203
  return match.group(1).strip().replace('*', '').replace('`', '').strip()
204
  return "N/A"
205
  except: return "N/A"
206
 
207
+ async def call_gemini(prompt, attachment=None):
208
+ """
209
+ [Core Logic] 8 密钥池高频轮询与故障自动迁移
210
+ """
211
+ if not KEY_POOL: return "ERROR: NO_KEYS"
212
 
213
+ img_data = None
214
  if attachment:
215
+ try: img_data = await attachment.read()
216
+ except: return "ERROR: READ_FAIL"
217
 
218
  loop = asyncio.get_running_loop()
219
+ shuffled = list(KEY_POOL)
220
+ random.shuffle(shuffled)
221
 
222
+ for api_key in shuffled:
223
  try:
224
  client = genai.Client(api_key=api_key)
225
  contents = [prompt]
226
+ if img_data:
227
+ contents.append(types.Part.from_bytes(data=img_data, mime_type='image/png'))
228
 
229
  response = await loop.run_in_executor(
230
  None,
231
  functools.partial(client.models.generate_content, model="gemini-1.5-flash", contents=contents)
232
  )
233
+ if response and response.text:
234
+ return response.text
 
235
  except Exception as e:
236
+ if "429" in str(e):
237
+ logger.warning("Key Rate Limited. Retrying with next...")
 
238
  continue
239
+ logger.error(f"Gemini API Error: {e}")
240
  continue
241
 
242
  return "ERROR: ALL_KEYS_EXHAUSTED"
243
 
244
  # ==============================================================================
245
+ # 5. 模块一:全网天梯 (Rank & Stats)
246
  # ==============================================================================
247
  @bot.command()
248
  async def profile(ctx, member: discord.Member = None):
249
+ t = member or ctx.author
250
+ u = bk_engine.get_user(t.id)
251
  elo = u['elo']
252
 
253
+ # 动态段位
254
+ if elo < 1300: rank, color = "Iron 学徒 🗑️", 0x717d7e
255
+ elif elo < 1500: rank, color = "Bronze 资深 🥉", 0xcd7f32
256
+ elif elo < 1800: rank, color = "Silver 精英 🥈", 0xbdc3c7
257
+ elif elo < 2100: rank, color = "Gold 专家 🥇", 0xf1c40f
258
+ elif elo < 2500: rank, color = "Platinum 大师 💎", 0x3498db
259
+ else: rank, color = "Grandmaster 传奇 👑", 0x9b59b6
260
+
261
+ emb = discord.Embed(title=f"📊 Quant Profile: {t.display_name}", color=color)
262
+ emb.add_field(name="Rating (ELO)", value=f"**{elo}** ({rank})", inline=True)
263
+ emb.add_field(name="Assets", value=f"💰 **{u['points']}**", inline=True)
264
 
265
  f_info = u['faction'] or "Free Agent"
266
+ f_emoji = bk_engine.data["factions"].get(f_info, {}).get("emoji", "🌐")
267
  emb.add_field(name="Faction", value=f"{f_emoji} {f_info}", inline=True)
268
 
269
  stats = (
 
271
  f"Peak Score: {u['highest_score']:.1f}\n"
272
  f"Max MDD: {u.get('mdd_record',0):.2f}%"
273
  )
274
+ emb.add_field(name="Institutional Metrics", value=f"```yaml\n{stats}\n```", inline=False)
275
 
276
+ if target_avatar := t.avatar:
277
+ emb.set_thumbnail(url=target_avatar.url)
278
+ emb.set_footer(text=f"Node: HuggingFace Titan | Session: {SESSION_ID}")
279
  await ctx.send(embed=emb)
280
 
281
  @bot.command()
282
  async def leaderboard(ctx):
283
+ all_users = []
284
+ for uid, d in bk_engine.data["users"].items():
285
+ all_users.append({"id": uid, "elo": d["elo"], "score": d["highest_score"]})
286
 
287
+ top = sorted(all_users, key=lambda x: x["elo"], reverse=True)[:10]
288
  desc = ""
289
+ for idx, u in enumerate(top):
290
+ icon = ["🥇","🥈","🥉"][idx] if idx < 3 else f"{idx+1}."
291
  desc += f"{icon} <@{u['id']}> : **{u['elo']}** (Peak: {u['score']:.1f})\n"
292
 
293
+ emb = discord.Embed(title="🏆 BK-GTA Institutional Leaderboard", description=desc or "No data.", color=0xf1c40f)
294
 
295
+ f_info = ""
296
  for k, v in bk_engine.data["factions"].items():
297
+ f_info += f"{v['emoji']} {k}: {v['score']} pts "
298
+ if f_info:
299
+ emb.add_field(name="⚔️ Faction War", value=f_info, inline=False)
300
+
301
  await ctx.send(embed=emb)
302
 
303
  # ==============================================================================
304
+ # 6. 模块二:宏观情报 (Macro Analysis)
305
  # ==============================================================================
306
  @bot.command()
307
  async def news(ctx):
308
+ if not ctx.message.attachments: return await ctx.send("❌ Error: 请上传情报截图。")
309
+ m = await ctx.send("📡 **[Sentinel] Analyzing Macro Pulse...**")
310
 
311
  prompt = (
312
+ "你是一名顶级对冲基金策略师。请解析截图内容:\n"
313
+ "1. 评分 (SCORE): -10 (极大利空) +10 (极大利多)。\n"
314
+ "2. 总结 (SUMMARY): 30字内概括核心变量。\n"
315
+ "3. 解读 (TAKE): 机构视角的隐含逻辑。\n"
316
+ "格式必须为:\nSCORE: [v]\nSUMMARY: [v]\nTAKE: [v]"
317
  )
318
+ res = await call_gemini(prompt, ctx.message.attachments[0])
319
 
320
  score = safe_extract_float(res, "SCORE")
321
  summary = safe_extract_text(res, "SUMMARY", "TAKE")
322
  take = safe_extract_text(res, "TAKE")
323
 
324
+ color = 0x2ecc71 if score > 0 else 0xe74c3c if score < 0 else 0xbdc3c7
325
+ emb = discord.Embed(title="📰 Macro Intelligence Analysis", color=color)
326
+ emb.add_field(name="Sentiment Score", value=f"**{score:+.1f} / 10**", inline=True)
327
+ emb.add_field(name="Core Catalyst", value=summary, inline=False)
328
+ emb.add_field(name="Institutional View", value=f"```\n{take}\n```", inline=False)
329
 
330
  await m.delete()
331
  await ctx.send(embed=emb)
332
 
333
  # ==============================================================================
334
+ # 7. 模块三:量化审计 (Institutional Audit)
335
  # ==============================================================================
336
  @bot.command()
337
  async def evaluate(ctx):
338
+ if not ctx.message.attachments: return await ctx.send("❌ Error: 请上传回测或结算截图。")
339
+
340
  if audit_lock.locked():
341
+ return await ctx.send("⚠️ **[System Busy]** 正在处理另一项审计,请稍后。")
342
 
343
  async with audit_lock:
344
+ m = await ctx.send("🛡️ **[Audit Engine] 启动深度扫描... (锁闭模式)**")
345
  prompt = (
346
+ "你是一名顶级量化风险官。请提取并审计以下指标:\n"
347
+ "RET: 收益率, MDD: 最大回撤, PF: 盈利因子, SHARPE: 夏普比率, TRADES: 总笔数, S_BASE: 基础评分(0-100)。\n"
348
+ " MDD > 35% 或交易笔数 < 10,则 VIOLATION 为 TRUE。\n"
349
+ "H_ZH: 中文核心优势或致命风险概括。\n"
350
+ "格式:\nRET: [v]\nMDD: [v]\nPF: [v]\nSHARPE: [v]\nTRADES: [v]\nS_BASE: [v]\nVIOLATION: [TRUE/FALSE]\nH_ZH: [v]"
351
  )
352
 
353
+ res = await call_gemini(prompt, ctx.message.attachments[0])
354
 
355
  if "ERROR" in res:
356
  await m.delete()
357
+ return await ctx.send("🔴 [Audit Crash] 引擎请求失败。")
358
 
359
  ret = safe_extract_float(res, "RET")
360
  mdd = safe_extract_float(res, "MDD")
 
361
  sharpe = safe_extract_float(res, "SHARPE")
362
  trades = int(safe_extract_float(res, "TRADES"))
363
  s_base = safe_extract_float(res, "S_BASE")
364
  violation = "TRUE" in res.upper()
365
 
366
+ # 风控核心:MDD 否决制
367
  if violation or mdd > 35.0:
368
  s_final = 0.0
369
+ status = "REJECTED (High Risk)"
370
  color = 0xe74c3c
371
  else:
372
+ # 复合评分逻辑:基础分 * 样本权重 * 风险权重
373
+ weight_sample = math.log10(max(trades, 1)) / 1.5
374
+ weight_risk = (sharpe / 2.0) if sharpe > 0 else 0.5
375
+ s_final = min(max(s_base * weight_sample * weight_risk, 0.0), 100.0)
376
+ status = "✅ AUDIT CLEARED"
377
  color = 0x2ecc71
378
 
379
+ bk_engine.update_audit(ctx.author.id, s_final, mdd, sharpe)
380
+ highlights = safe_extract_text(res, "H_ZH")
381
 
382
+ emb = discord.Embed(title="🛡️ Quantitative Audit Report", color=color)
383
+ emb.add_field(name="Verdict", value=f"**{status}**", inline=False)
384
+ metrics = f"Return: {ret}% | MDD: {mdd}%\nSharpe: {sharpe} | Trades: {trades}"
385
+ emb.add_field(name="Key Metrics", value=f"```\n{metrics}\n```", inline=False)
386
+ emb.add_field(name="Logic Score", value=f"**{s_final:.2f} / 100**", inline=True)
387
+ emb.add_field(name="Highlights", value=highlights, inline=False)
388
 
389
  await m.delete()
390
  await ctx.send(embed=emb)
391
 
392
  # ==============================================================================
393
+ # 8. 模块四:博弈盘口 (Institutional Betting)
394
  # ==============================================================================
395
  @bot.command()
396
  @commands.has_permissions(administrator=True)
397
  async def open_bet(ctx, *, topic):
398
  bid = str(random.randint(100, 999))
399
  bk_engine.active_bets[bid] = {
400
+ "topic": topic, "up": 0, "down": 0,
401
+ "u_up": {}, "u_down": {}, "status": "OPEN",
402
+ "time": str(datetime.datetime.now())
 
 
403
  }
404
+ await ctx.send(f"🎰 **[Bet-ID:{bid}]** 新博弈盘口已开启!\n主题:**{topic}**\n输入 `!bet {bid} UP/DOWN [金额]` 参与。")
405
 
406
  @bot.command()
407
  async def bet(ctx, bid: str, side: str, amt: int):
408
+ if bid not in bk_engine.active_bets or bk_engine.active_bets[bid]["status"] != "OPEN":
409
+ return await ctx.send("❌ 盘口不存在或已关闭。")
410
+
411
  u = bk_engine.get_user(ctx.author.id)
412
+ if u["points"] < amt or amt <= 0:
413
+ return await ctx.send("❌ 资产点数不足。")
414
 
415
+ side = side.upper()
416
+ if side not in ["UP", "DOWN"]: return await ctx.send("❌ 必须选择 UP DOWN")
417
 
418
+ u["points"] -= amt
419
  b = bk_engine.active_bets[bid]
420
+ pool = "u_up" if side == "UP" else "u_down"
421
  b[side.lower()] += amt
422
  b[pool][str(ctx.author.id)] = b[pool].get(str(ctx.author.id), 0) + amt
423
+
424
  bk_engine.save_db()
425
+ await ctx.send(f"✅ 已确认:{ctx.author.name} {side} 注入 {amt} 点资产。")
426
 
427
  @bot.command()
428
  @commands.has_permissions(administrator=True)
429
  async def settle_bet(ctx, bid: str, winner: str):
430
  if bid not in bk_engine.active_bets: return
431
+ b = bk_engine.active_bets[bid]
432
+ winner = winner.upper()
433
 
434
  win_pool = b["u_up"] if winner == "UP" else b["u_down"]
435
  total_w = b["up"] if winner == "UP" else b["down"]
 
437
 
438
  if total_w > 0:
439
  for uid, amt in win_pool.items():
440
+ profit = (amt / total_w) * total_l
441
  bk_engine.get_user(int(uid))["points"] += int(amt + profit)
442
 
443
  b["status"] = "SETTLED"
444
  bk_engine.save_db()
445
+ await ctx.send(f"🏁 盘口 {bid} 已结算!胜利方:**{winner}**。资产已自动划转。")
446
 
447
  # ==============================================================================
448
+ # 9. 模块五:PVP 竞技场 (Duel Arena)
449
  # ==============================================================================
450
  @bot.command()
451
  async def challenge(ctx, member: discord.Member):
452
  if member.id == ctx.author.id: return
453
+ did = f"D-{random.randint(1000, 9999)}"
454
+ bk_engine.pending_challenges[member.id] = {"challenger": ctx.author.id, "id": did}
455
+ await ctx.send(f"⚔️ {ctx.author.name} {member.mention} 发起挑战!\nID: `{did}`. 输入 `!accept` 接受。")
456
 
457
  @bot.command()
458
  async def accept(ctx):
459
+ if ctx.author.id not in bk_engine.pending_challenges:
460
+ return await ctx.send("❌ 没有待处理的决斗请求。")
461
+
462
  req = bk_engine.pending_challenges.pop(ctx.author.id)
463
+ did = req["id"]
464
+ bk_engine.active_duels[did] = {
465
+ "p1": req["challenger"], "p2": ctx.author.id,
466
+ "s1": None, "s2": None, "t": str(datetime.datetime.now())
467
+ }
468
+ await ctx.send(f"🔥 **竞技场锁定!** 决斗 ID: `{did}`\n请双方通过 `!duel_submit {did}` 提交审计截图。")
469
 
470
  @bot.command()
471
  async def duel_submit(ctx, did: str):
 
475
  duel = bk_engine.active_duels[did]
476
  if ctx.author.id not in [duel["p1"], duel["p2"]]: return
477
 
478
+ m = await ctx.send("🔍 正在审计决斗数据...")
479
+ res = await call_gemini("Extract SCORE:[0-100]. Format: SCORE:[v]", ctx.message.attachments[0])
480
  score = safe_extract_float(res, "SCORE")
481
 
482
  if ctx.author.id == duel["p1"]: duel["s1"] = score
 
486
  p1, p2 = duel["p1"], duel["p2"]
487
  s1, s2 = duel["s1"], duel["s2"]
488
  wid, lid = (p1, p2) if s1 > s2 else (p2, p1)
489
+ w_elo, l_elo, streak = bk_engine.update_elo(wid, lid)
490
 
491
+ emb = discord.Embed(title="🏆 Duel Arena Verdict", color=0xf1c40f)
492
+ emb.add_field(name="Winner", value=f"<@{wid}>\nScore: {max(s1,s2)}\nELO: {w_elo}", inline=True)
493
+ emb.add_field(name="Loser", value=f"<@{lid}>\nScore: {min(s1,s2)}\nELO: {l_elo}", inline=True)
494
+ emb.set_footer(text=f"Win Streak: {streak} | Duel ID: {did}")
495
  await ctx.send(embed=emb)
496
  del bk_engine.active_duels[did]
497
  else:
498
+ await ctx.send(f"✅ 交易员 <@{ctx.author.id}> 分数已锁定 ({score})。等待对手提交。")
499
  await m.delete()
500
 
501
  # ==============================================================================
502
+ # 10. 模块六:海报渲染引擎 (Poster Rendering)
503
  # ==============================================================================
504
  @bot.command()
505
  async def viral_audit(ctx):
506
  """
507
+ [完版] 提取审计数据并渲染具备像素级视觉逻辑的海报
508
  """
509
+ if not ctx.message.attachments: return
510
+ m = await ctx.send("🎨 **[GPU-Node] 正在渲染视觉海报...**")
 
 
511
 
512
+ p = "Extract RET: [v] and SHARPE: [v]. Format: RET: [v] SHARPE: [v]"
513
+ res = await call_gemini(p, ctx.message.attachments[0])
514
  ret, sharpe = safe_extract_float(res, "RET"), safe_extract_float(res, "SHARPE")
515
 
516
  try:
517
  W, H = 600, 800
518
+ # 暗色底板
519
+ base = Image.new('RGB', (W, H), (18, 20, 30))
520
  draw = ImageDraw.Draw(base)
521
 
522
+ # 绘制背景装饰网格 (逻辑恢复)
523
  for i in range(0, W, 40): draw.line([(i, 0), (i, H)], fill=(30, 35, 45), width=1)
524
  for j in range(0, H, 40): draw.line([(0, j), (W, j)], fill=(30, 35, 45), width=1)
525
 
526
+ # 边框
527
  draw.rectangle([10, 10, 590, 790], outline=(46, 204, 113), width=2)
528
  draw.rectangle([20, 20, 580, 780], outline=(46, 204, 113), width=5)
529
 
530
+ # 文字渲染
531
  draw.text((40, 60), "BK-GTA QUANTITATIVE SYSTEM", fill=(255, 255, 255))
532
+ draw.text((40, 150), f"TRADER: {ctx.author.name.upper()}", fill=(46, 204, 113))
533
+ draw.text((40, 260), f"ALPHA RET: +{ret}%", fill=(255, 255, 255))
534
+ draw.text((40, 360), f"SHARPE: {sharpe}", fill=(255, 255, 255))
535
+ draw.text((40, 460), f"SESSION: {SESSION_ID}", fill=(100, 100, 110))
536
+ draw.text((40, 720), "VERIFIED BY SENTINEL AI", fill=(46, 204, 113))
 
537
 
538
  fp = io.BytesIO()
539
  base.save(fp, 'PNG')
540
  fp.seek(0)
541
  await ctx.send(file=discord.File(fp, filename=f"Viral_{ctx.author.id}.png"))
 
542
  except Exception as e:
543
  logger.error(f"Render Error: {e}")
544
+ await ctx.send(f"❌ 渲染引擎崩溃: `{e}`")
545
  finally:
546
  await m.delete()
547
 
548
  # ==============================================================================
549
+ # 11. 系统守护与自备份链 (Maintenance)
550
  # ==============================================================================
551
  @tasks.loop(hours=1)
552
  async def auto_backup():
553
  bk_engine.save_db()
554
+ logger.info("💾 [System] Hourly Database Backup Executed.")
555
 
556
  @bot.event
557
  async def on_ready():
558
  if not auto_backup.is_running(): auto_backup.start()
559
+ await bot.change_presence(activity=discord.Activity(type=discord.ActivityType.watching, name="HuggingFace Node"))
560
+ logger.info(f"✅ [Online] BK-GTA V62.9-Titan Live. Session: {SESSION_ID}")
561
 
562
+ def run_bot():
563
+ if not DISCORD_TOKEN:
564
+ logger.critical("❌ [FATAL] DISCORD_TOKEN Missing.")
565
+ return
566
+
567
+ # 自动重连容器
568
+ while True:
569
  try:
570
  bot.run(DISCORD_TOKEN)
571
  except Exception as e:
572
+ logger.error(f"⚠️ [Socket Error] {e}. Restarting in 10s...")
573
+ time.sleep(10)
 
574
 
575
  if __name__ == "__main__":
576
+ # 1. 启动 Discord 后台线程
577
+ threading.Thread(target=run_bot, daemon=True).start()
578
 
579
+ # 2. 启动 Gradio 前台哨兵 (强制监听 7860 端口)
 
580
  iface = gr.Interface(
581
  fn=get_status,
582
  inputs=None,
583
  outputs="json",
584
+ title="BK-GTA Sentinel AI",
585
+ description="Physical architecture monitor. Logic Integrity: Secure."
586
  )
587
  iface.launch(server_name="0.0.0.0", server_port=7860)