hualinxin8615 commited on
Commit
cefcd1e
·
verified ·
1 Parent(s): 55c78cd

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +500 -365
app.py CHANGED
@@ -19,47 +19,154 @@ ALLOCATION_LOCK_DURATION = 5 # Lock duration in minutes
19
  # 缓存配置
20
  ACTIVITY_CACHE_FILE = "space_activity_cache.json"
21
  CACHE_UPDATE_INTERVAL = 600 # 10分钟 = 600秒
 
22
  CACHE_LOCK = threading.Lock()
23
 
24
- # 修改 SpaceActivityCache 类的相关方法
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
25
 
26
  class SpaceActivityCache:
27
- def __init__(self, repo_dir):
28
- self.repo_dir = repo_dir
29
- self.cache_file_path = os.path.join(repo_dir, ACTIVITY_CACHE_FILE)
30
  self._cache_data = {}
31
  self._last_update = None
32
  self._update_thread = None
33
  self._stop_event = threading.Event()
34
- self._is_initialized = False
35
 
36
- # 启动时先尝试从文件加载缓存,而不是立即更新
37
- self._load_cache_from_file_on_init()
38
 
39
  # 启动后台更新线程
40
  self.start_background_updates()
41
 
42
- def _load_cache_from_file_on_init(self):
43
- """初始化时从文件加载缓存"""
44
- try:
45
- if os.path.exists(self.cache_file_path):
46
- with open(self.cache_file_path, 'r', encoding='utf-8') as f:
47
- self._cache_data = json.load(f)
48
- if 'last_updated' in self._cache_data:
49
- self._last_update = datetime.fromisoformat(self._cache_data['last_updated'])
50
- self._is_initialized = True
51
- print(f"[SpaceActivityCache] Cache loaded from file on init: {len(self._cache_data.get('spaces', {}))} spaces")
52
- else:
53
- print("[SpaceActivityCache] Invalid cache file format")
54
- else:
55
- print("[SpaceActivityCache] No cache file found on init")
56
- except Exception as e:
57
- print(f"[SpaceActivityCache] Error loading cache on init: {e}")
58
-
59
- # 如果没有有效缓存,标记为需要初始更新
60
- if not self._is_initialized:
61
- print("[SpaceActivityCache] No valid cache found, will update on first background cycle")
62
-
63
  def start_background_updates(self):
64
  """启动后台缓存更新线程"""
65
  if self._update_thread and self._update_thread.is_alive():
@@ -70,19 +177,14 @@ class SpaceActivityCache:
70
  self._update_thread.start()
71
  print("[SpaceActivityCache] Background update thread started")
72
 
 
 
 
 
 
 
73
  def _background_update_worker(self):
74
- """后台更新工作线程 - 修改启动逻辑"""
75
- # 如果没有初始化,等待30秒后进行第一次更新
76
- if not self._is_initialized:
77
- print("[SpaceActivityCache] Waiting 30 seconds before first cache update...")
78
- if self._stop_event.wait(timeout=30):
79
- return # 收到停止信号
80
-
81
- print("[SpaceActivityCache] Performing initial cache update...")
82
- self._update_cache()
83
- self._is_initialized = True
84
-
85
- # 正常的定期更新循环
86
  while not self._stop_event.is_set():
87
  try:
88
  # 等待指定间隔或停止信号
@@ -95,25 +197,136 @@ class SpaceActivityCache:
95
 
96
  except Exception as e:
97
  print(f"[SpaceActivityCache] Error in background update: {e}")
98
- if not self._stop_event.wait(timeout=60):
99
  continue
100
 
101
- def get_space_activity(self, space_name):
102
- """获取指定空间的活动信息 - 不触发立即更新"""
103
- with CACHE_LOCK:
104
- # 如果缓存为空但已经有缓存文件,尝试加载
105
- if not self._cache_data and os.path.exists(self.cache_file_path):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  try:
107
- with open(self.cache_file_path, 'r', encoding='utf-8') as f:
108
- self._cache_data = json.load(f)
109
- print("[SpaceActivityCache] Cache loaded from file in get_space_activity")
110
- except Exception as e:
111
- print(f"[SpaceActivityCache] Error loading cache in get_space_activity: {e}")
 
 
 
 
 
 
 
 
 
 
 
112
 
113
- # 如果仍然没有缓存数据,返回默认值而不是触发更新
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  if not self._cache_data or 'spaces' not in self._cache_data:
115
- print(f"[SpaceActivityCache] No cache data available for {space_name}, returning defaults")
116
- return datetime.min, "no_cache_available"
 
 
 
 
117
 
118
  spaces_data = self._cache_data.get('spaces', {})
119
  space_info = spaces_data.get(space_name, {})
@@ -135,25 +348,17 @@ class SpaceActivityCache:
135
  return last_activity, status
136
 
137
  def get_all_spaces_activity(self):
138
- """获取所有空间的活动信息 - 不触发立即更新"""
139
  with CACHE_LOCK:
140
- # 如果缓存为空但已经有缓存文件,尝试加载
141
- if not self._cache_data and os.path.exists(self.cache_file_path):
142
- try:
143
- with open(self.cache_file_path, 'r', encoding='utf-8') as f:
144
- self._cache_data = json.load(f)
145
- print("[SpaceActivityCache] Cache loaded from file in get_all_spaces_activity")
146
- except Exception as e:
147
- print(f"[SpaceActivityCache] Error loading cache in get_all_spaces_activity: {e}")
148
 
149
  result = {}
150
  spaces_data = self._cache_data.get('spaces', {})
151
 
152
- # 如果没有缓存数据,返回空字典而不是触发更新
153
- if not spaces_data:
154
- print("[SpaceActivityCache] No cached spaces data available, returning empty dict")
155
- return result
156
-
157
  for space_name, space_info in spaces_data.items():
158
  last_activity_str = space_info.get('last_activity')
159
  if last_activity_str:
@@ -183,7 +388,7 @@ class SpaceActivityCache:
183
  'last_updated': last_updated,
184
  'age_minutes': age_minutes,
185
  'spaces_count': len(self._cache_data.get('spaces', {})),
186
- 'is_fresh': age_minutes < (CACHE_UPDATE_INTERVAL / 60) * 1.5 # 允许1.5倍的间隔
187
  }
188
  except:
189
  pass
@@ -200,107 +405,6 @@ class SpaceActivityCache:
200
  print("[SpaceActivityCache] Force updating cache...")
201
  self._update_cache()
202
 
203
- # 全局缓存实例
204
- activity_cache = None
205
-
206
- def init_activity_cache(repo_dir):
207
- """初始化活动缓存"""
208
- global activity_cache
209
- if activity_cache is None:
210
- activity_cache = SpaceActivityCache(repo_dir)
211
- print("[init_activity_cache] Activity cache initialized")
212
- return activity_cache
213
-
214
- # 修改 analyze_space_activity_cached 函数
215
- def analyze_space_activity_cached(available_spaces, repo_dir):
216
- """使用缓存的空间活动分析 - 改进版本"""
217
- global activity_cache
218
-
219
- # 确保缓存已初始化
220
- if activity_cache is None:
221
- activity_cache = init_activity_cache(repo_dir)
222
-
223
- space_activity = []
224
- current_time = datetime.now()
225
-
226
- # 获取缓存信息
227
- cache_info = activity_cache.get_cache_info()
228
- print(f"[analyze_space_activity_cached] Using cache (age: {cache_info['age_minutes']:.1f} min, fresh: {cache_info['is_fresh']})")
229
-
230
- # 获取所有空间的缓存活动数据
231
- all_spaces_activity = activity_cache.get_all_spaces_activity()
232
-
233
- # 如果缓存完全为空,为所有空间提供默认值
234
- if not all_spaces_activity:
235
- print("[analyze_space_activity_cached] No cached data available, using default values for all spaces")
236
- for space_name in available_spaces:
237
- all_spaces_activity[space_name] = {
238
- 'last_activity': datetime.min,
239
- 'status': 'no_cache_data'
240
- }
241
-
242
- # Read allocation records to filter out recently allocated spaces
243
- active_allocations = read_allocation_records(repo_dir)
244
-
245
- print(f"[analyze_space_activity_cached] Analyzing {len(available_spaces)} spaces using cached data...")
246
-
247
- for space_name in available_spaces:
248
- # 从缓存获取活动信息
249
- cached_info = all_spaces_activity.get(space_name)
250
-
251
- if cached_info:
252
- last_activity = cached_info['last_activity']
253
- cached_status = cached_info['status']
254
- else:
255
- # 缓存中没有这个空间,使用默认值
256
- last_activity = datetime.min
257
- cached_status = "not_in_cache"
258
-
259
- # Calculate idle time in minutes
260
- if last_activity == datetime.min or 'empty' in cached_status or 'not_found' in cached_status or 'no_cache' in cached_status:
261
- idle_minutes = float('inf') # Never used or no data
262
- if 'no_cache' in cached_status:
263
- status = "No cache data available"
264
- else:
265
- status = "Never used"
266
- last_activity_str = "Never"
267
- else:
268
- idle_minutes = (current_time - last_activity).total_seconds() / 60
269
- status = f"Idle for {idle_minutes:.1f} minutes (cached)"
270
- last_activity_str = last_activity.strftime('%Y-%m-%d %H:%M:%S')
271
-
272
- # Check if space is recently allocated (remote)
273
- is_recently_allocated_remote = space_name in active_allocations
274
- if is_recently_allocated_remote:
275
- alloc_info = active_allocations[space_name]
276
- minutes_until_free = (alloc_info['expires_at'] - current_time).total_seconds() / 60
277
- status += f" (Recently allocated remotely, free in {minutes_until_free:.1f} min)"
278
-
279
- # Check if space is recently allocated (local)
280
- is_recently_allocated_local, local_student = local_tracker.is_recently_allocated_locally(space_name)
281
- if is_recently_allocated_local:
282
- status += f" (Recently allocated locally to {local_student})"
283
-
284
- space_activity.append({
285
- 'space_name': space_name,
286
- 'last_activity': last_activity,
287
- 'last_activity_str': last_activity_str,
288
- 'idle_minutes': idle_minutes,
289
- 'status': status,
290
- 'is_recently_allocated_remote': is_recently_allocated_remote,
291
- 'is_recently_allocated_local': is_recently_allocated_local,
292
- 'cached_status': cached_status
293
- })
294
-
295
- print(f"[analyze_space_activity_cached] {space_name}: {status}")
296
-
297
- # Sort by idle time (most idle first)
298
- space_activity.sort(key=lambda x: x['idle_minutes'], reverse=True)
299
-
300
- return space_activity
301
-
302
-
303
-
304
  # 新增:本地防撞车机制
305
  class LocalAllocationTracker:
306
  def __init__(self):
@@ -376,117 +480,102 @@ class LocalAllocationTracker:
376
  return summary
377
 
378
  # 全局实例
 
 
379
  local_tracker = LocalAllocationTracker()
380
 
381
- # Environment variables
382
- HF_HUB_TOKEN = os.environ.get("HF_HUB_TOKEN", None)
383
- if HF_HUB_TOKEN is None:
384
- raise ValueError("Set HF_HUB_TOKEN in Space Settings -> Secrets")
385
-
386
- def init_data_storage_connection():
387
- """Initialize connection to centralized data storage repository"""
388
- try:
389
- repo = Repository(
390
- local_dir=LOCAL_DATA_DIR,
391
- clone_from=DATA_STORAGE_REPO,
392
- revision=DATA_BRANCH_NAME,
393
- repo_type="space",
394
- use_auth_token=HF_HUB_TOKEN
395
- )
396
- # Configure git user
397
- repo.git_config_username_and_email("git_user", f"load_distributor")
398
- repo.git_config_username_and_email("git_email", f"loaddistributor@takeiteasy.space")
399
-
400
- print(f"[init_data_storage_connection] Pulling latest data from {DATA_STORAGE_REPO}...")
401
- repo.git_pull(rebase=True)
402
-
403
- print(f"[init_data_storage_connection] Successfully connected to data storage")
404
- print(f"[init_data_storage_connection] Local directory: {LOCAL_DATA_DIR}")
405
- print(f"[init_data_storage_connection] Branch: {DATA_BRANCH_NAME}")
406
-
407
- return repo
408
-
409
- except Exception as e:
410
- print(f"[init_data_storage_connection] Error: {e}")
411
- return None
412
 
413
- def get_available_spaces(repo_dir):
414
- """Dynamically get space list from CIV3283/Data_Storage"""
415
- available_spaces = set()
416
 
417
- try:
418
- # Scan all *_query_log.csv files
419
- for filename in os.listdir(repo_dir):
420
- if filename.endswith('_query_log.csv') and '_Student_' in filename:
421
- # Extract space name from filename: CIV3283_Student_01_query_log.csv → CIV3283_Student_01
422
- space_name = filename.replace('_query_log.csv', '')
423
- available_spaces.add(space_name)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
424
 
425
- spaces_list = sorted(list(available_spaces))
426
- print(f"[get_available_spaces] Found {len(spaces_list)} student spaces: {spaces_list}")
427
- return spaces_list
 
 
 
 
428
 
429
- except Exception as e:
430
- print(f"[get_available_spaces] Error: {e}")
431
- return []
432
-
433
- def get_last_activity_time(csv_file_path):
434
- """Get the timestamp of the last activity from query log file using proper CSV parsing"""
435
- try:
436
- if not os.path.exists(csv_file_path):
437
- print(f"[get_last_activity_time] File not found: {csv_file_path}")
438
- return datetime.min # Never used
439
 
440
- # Use proper CSV reader to handle multiline cells
441
- with open(csv_file_path, 'r', encoding='utf-8') as f:
442
- csv_reader = csv.reader(f)
443
-
444
- # Skip header row
445
- try:
446
- header = next(csv_reader)
447
- print(f"[get_last_activity_time] CSV header: {header}")
448
- except StopIteration:
449
- print(f"[get_last_activity_time] Empty file: {csv_file_path}")
450
- return datetime.min
451
-
452
- # Read all data rows
453
- rows = []
454
- try:
455
- for row in csv_reader:
456
- if row: # Skip empty rows
457
- rows.append(row)
458
- except Exception as csv_error:
459
- print(f"[get_last_activity_time] CSV parsing error: {csv_error}")
460
- return datetime.min
461
 
462
- if not rows:
463
- print(f"[get_last_activity_time] No data rows in file: {csv_file_path}")
464
- return datetime.min # No data
 
465
 
466
- # Get the last row (most recent entry)
467
- last_row = rows[-1]
468
- print(f"[get_last_activity_time] Last row has {len(last_row)} columns")
469
- print(f"[get_last_activity_time] Last row preview: {[col[:50] + '...' if len(col) > 50 else col for col in last_row[:3]]}")
 
 
 
 
 
 
470
 
471
- # Parse timestamp from the 3rd column (index 2)
472
- if len(last_row) >= 3:
473
- timestamp_str = last_row[2].strip() # timestamp column
474
- try:
475
- parsed_time = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S')
476
- print(f"[get_last_activity_time] Parsed timestamp: {parsed_time}")
477
- return parsed_time
478
- except ValueError as ve:
479
- print(f"[get_last_activity_time] Date parsing error for '{timestamp_str}': {ve}")
480
- return datetime.min
481
- else:
482
- print(f"[get_last_activity_time] Invalid row format - expected ≥3 columns, got {len(last_row)}")
483
- return datetime.min
484
-
485
- except Exception as e:
486
- print(f"[get_last_activity_time] Error reading {csv_file_path}: {e}")
487
- import traceback
488
- print(f"[get_last_activity_time] Traceback: {traceback.format_exc()}")
489
- return datetime.min
490
 
491
  def read_allocation_records(repo_dir):
492
  """Read and parse allocation records, returning only non-expired allocations"""
@@ -630,30 +719,10 @@ def simple_push_allocation_record(repo, space_name, student_id):
630
  print(f"[simple_push_allocation_record] Allocation recorded locally, sync will happen eventually")
631
  return False
632
 
633
-
634
-
635
- # 修改状态显示函数,处理无缓存数据的情况
636
- def create_status_display_with_cache_info(space_activity):
637
- """创建包含缓存信息的状态显示 - 改进版本"""
638
- global activity_cache
639
-
640
- # 获取缓存状态
641
- cache_info = activity_cache.get_cache_info() if activity_cache else None
642
-
643
  status_display = "📊 **Current Space Status (sorted by availability):**<br><br>"
644
 
645
- # 显示缓存信息
646
- if cache_info:
647
- if cache_info['is_fresh']:
648
- cache_status = f"✅ Fresh (updated {cache_info['age_minutes']:.1f} min ago)"
649
- elif cache_info['last_updated']:
650
- cache_status = f"⚠️ Stale (updated {cache_info['age_minutes']:.1f} min ago)"
651
- else:
652
- cache_status = f"🔄 No cache data (background update in progress)"
653
-
654
- status_display += f"🔄 **Cache Status:** {cache_status}<br>"
655
- status_display += f"📋 **Cached Spaces:** {cache_info['spaces_count']}<br><br>"
656
-
657
  # 显示本地分配记录摘要
658
  local_summary = local_tracker.get_recent_allocations_summary()
659
  if local_summary:
@@ -665,18 +734,11 @@ def create_status_display_with_cache_info(space_activity):
665
  for i, space in enumerate(space_activity, 1):
666
  status_display += f"{i}. **{space['space_name']}**<br>"
667
  status_display += f"&nbsp;&nbsp;&nbsp;• Status: {space['status']}<br>"
668
- status_display += f"&nbsp;&nbsp;&nbsp;• Last activity: {space['last_activity_str']}<br>"
669
-
670
- # 显示缓存状态
671
- if 'cached_status' in space:
672
- status_display += f"&nbsp;&nbsp;&nbsp;• Cache: {space['cached_status']}<br>"
673
-
674
- status_display += "<br>"
675
 
676
  return status_display
677
 
678
- # 修改select_space函数以使用新的状态显示
679
- def select_space_with_enhanced_collision_avoidance_cached(space_activity, student_id, repo):
680
  """使用缓存的增强防撞空间选择函数"""
681
 
682
  print(f"[select_space_with_enhanced_collision_avoidance_cached] Starting selection for student: {student_id}")
@@ -748,7 +810,10 @@ def select_space_with_enhanced_collision_avoidance_cached(space_activity, studen
748
  print(f"[select_space_with_enhanced_collision_avoidance_cached] Local allocation recorded BEFORE file write")
749
 
750
  # 第五步:记录到文件和远程
751
- write_success = write_allocation_record(space_name, student_id, repo.local_dir)
 
 
 
752
  if write_success:
753
  push_success = simple_push_allocation_record(repo, space_name, student_id)
754
  if not push_success:
@@ -764,21 +829,6 @@ def select_space_with_enhanced_collision_avoidance_cached(space_activity, studen
764
 
765
  return redirect_to_space(redirect_url, selected_space, status_display)
766
 
767
- def get_cache_status():
768
- """获取缓存状态(用于调试或管理)"""
769
- global activity_cache
770
- if activity_cache:
771
- return activity_cache.get_cache_info()
772
- return {"error": "Cache not initialized"}
773
-
774
- def force_cache_update():
775
- """强制更新缓存(用于调试或管理)"""
776
- global activity_cache
777
- if activity_cache:
778
- activity_cache.force_update()
779
- return {"status": "Cache updated"}
780
- return {"error": "Cache not initialized"}
781
-
782
  def redirect_to_space(redirect_url, selected_space, status_display):
783
  """Display redirect information with manual click option"""
784
 
@@ -841,28 +891,18 @@ def redirect_to_space(redirect_url, selected_space, status_display):
841
  return gr.HTML(redirect_html)
842
 
843
  def load_balance_user_cached(student_id):
844
- """使用缓存的负载均衡函数"""
845
  print(f"[load_balance_user_cached] Starting cached load balancing for student ID: {student_id}")
846
 
847
- # Initialize connection to data storage
848
- repo = init_data_storage_connection()
849
- if not repo:
850
- raise gr.Error("🚫 Unable to connect to the data storage system. Please try again later.", duration=8)
851
-
852
- # 初始化活动缓存
853
- init_activity_cache(LOCAL_DATA_DIR)
854
-
855
- # Get available spaces dynamically
856
- available_spaces = get_available_spaces(LOCAL_DATA_DIR)
857
 
858
- if not available_spaces:
859
- raise gr.Error("🚫 No student learning assistant spaces found in the system. Please contact your instructor.", duration=8)
860
-
861
- # 使用缓存版本的分析函数
862
- space_activity = analyze_space_activity_cached(available_spaces, LOCAL_DATA_DIR)
863
 
864
  # Select space with enhanced collision avoidance
865
- return select_space_with_enhanced_collision_avoidance_cached(space_activity, student_id, repo)
866
 
867
  def get_url_params(request: gr.Request):
868
  """Extract URL parameters from request"""
@@ -951,18 +991,60 @@ def handle_user_access_cached(request: gr.Request):
951
  """
952
  return title, gr.HTML(error_html)
953
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
954
  # 添加可选的管理界面(用于调试)
955
  def create_admin_interface():
956
- """创建管理界面(可选)- 简化版本"""
957
  with gr.Blocks(title="CIV3283 Load Distributor - Admin") as admin_interface:
958
  gr.Markdown("# 🔧 CIV3283 Load Distributor - Admin Panel")
959
 
960
- with gr.Row():
961
- cache_status_btn = gr.Button("📊 Check Cache Status", variant="secondary")
962
- force_update_btn = gr.Button("🔄 Force Cache Update", variant="primary")
963
-
964
- cache_info_display = gr.JSON(label="Cache Information")
965
- status_message = gr.Markdown("")
 
 
 
 
 
 
 
 
 
966
 
967
  def check_cache_status():
968
  try:
@@ -995,14 +1077,55 @@ def create_admin_interface():
995
  except Exception as e:
996
  return {"error": str(e)}, f"❌ Error updating cache: {str(e)}"
997
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
998
  cache_status_btn.click(
999
  fn=check_cache_status,
1000
- outputs=[cache_info_display, status_message]
1001
  )
1002
 
1003
- force_update_btn.click(
1004
  fn=force_update,
1005
- outputs=[cache_info_display, status_message]
 
 
 
 
 
 
 
 
 
 
 
1006
  )
1007
 
1008
  return admin_interface
@@ -1026,24 +1149,36 @@ def create_main_interface():
1026
  if __name__ == "__main__":
1027
  import sys
1028
 
1029
- # 检查是否需要管理界面
1030
- enable_admin = "--admin" in sys.argv or os.environ.get("ENABLE_ADMIN", "false").lower() == "true"
1031
-
1032
- if enable_admin:
1033
- print("🔧 Starting with admin interface enabled")
1034
- # 创建带管理界面的组合界面
1035
- main_interface = create_main_interface()
1036
- admin_interface = create_admin_interface()
1037
 
1038
- # 使用TabbedInterface组合两个界面
1039
- combined_interface = gr.TabbedInterface(
1040
- [main_interface, admin_interface],
1041
- ["🎯 Load Distributor", "🔧 Admin Panel"],
1042
- title="CIV3283 Load Distributor System"
1043
- )
1044
- combined_interface.launch()
1045
- else:
1046
- print("🎯 Starting main interface only")
1047
- # 只启动主界面
1048
- main_interface = create_main_interface()
1049
- main_interface.launch()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
19
  # 缓存配置
20
  ACTIVITY_CACHE_FILE = "space_activity_cache.json"
21
  CACHE_UPDATE_INTERVAL = 600 # 10分钟 = 600秒
22
+ DATA_SYNC_INTERVAL = 600 # 数据同步间隔,10分钟 = 600秒
23
  CACHE_LOCK = threading.Lock()
24
 
25
+ # 全局数据管理器
26
+ class DataManager:
27
+ def __init__(self):
28
+ self.repo = None
29
+ self.available_spaces = []
30
+ self.last_data_sync = None
31
+ self._sync_lock = threading.Lock()
32
+ self._sync_thread = None
33
+ self._stop_event = threading.Event()
34
+ self.initialized = False
35
+
36
+ def initialize(self):
37
+ """初始化数据管理器 - 只在启动时调用一次"""
38
+ if self.initialized:
39
+ return True
40
+
41
+ try:
42
+ print("[DataManager] Initializing data manager...")
43
+
44
+ # 初始化仓库连接
45
+ self.repo = Repository(
46
+ local_dir=LOCAL_DATA_DIR,
47
+ clone_from=DATA_STORAGE_REPO,
48
+ revision=DATA_BRANCH_NAME,
49
+ repo_type="space",
50
+ use_auth_token=os.environ.get("HF_HUB_TOKEN")
51
+ )
52
+
53
+ # 配置git用户
54
+ self.repo.git_config_username_and_email("git_user", f"load_distributor")
55
+ self.repo.git_config_username_and_email("git_email", f"loaddistributor@takeiteasy.space")
56
+
57
+ # 初始数据同步
58
+ self._sync_data()
59
+
60
+ # 启动后台同步线程
61
+ self._start_background_sync()
62
+
63
+ self.initialized = True
64
+ print("[DataManager] Data manager initialized successfully")
65
+ return True
66
+
67
+ except Exception as e:
68
+ print(f"[DataManager] Initialization failed: {e}")
69
+ return False
70
+
71
+ def _start_background_sync(self):
72
+ """启动后台数据同步线程"""
73
+ if self._sync_thread and self._sync_thread.is_alive():
74
+ return
75
+
76
+ self._stop_event.clear()
77
+ self._sync_thread = threading.Thread(target=self._background_sync_worker, daemon=True)
78
+ self._sync_thread.start()
79
+ print("[DataManager] Background sync thread started")
80
+
81
+ def _background_sync_worker(self):
82
+ """后台同步工作线程"""
83
+ while not self._stop_event.is_set():
84
+ try:
85
+ # 等待指定间隔或停止信号
86
+ if self._stop_event.wait(timeout=DATA_SYNC_INTERVAL):
87
+ break
88
+
89
+ print("[DataManager] Starting scheduled data sync...")
90
+ self._sync_data()
91
+ print("[DataManager] Scheduled data sync completed")
92
+
93
+ except Exception as e:
94
+ print(f"[DataManager] Error in background sync: {e}")
95
+ if not self._stop_event.wait(timeout=60): # 1分钟后重试
96
+ continue
97
+
98
+ def _sync_data(self):
99
+ """同步数据 - 拉取最新数据并更新可用空间列表"""
100
+ with self._sync_lock:
101
+ try:
102
+ start_time = time.time()
103
+ print("[DataManager] Syncing data from remote repository...")
104
+
105
+ # 拉取最新数据
106
+ self.repo.git_pull(rebase=True)
107
+
108
+ # 更新可用空间列表
109
+ self.available_spaces = self._get_available_spaces()
110
+ self.last_data_sync = datetime.now()
111
+
112
+ elapsed_time = time.time() - start_time
113
+ print(f"[DataManager] Data sync completed in {elapsed_time:.2f}s, found {len(self.available_spaces)} spaces")
114
+
115
+ except Exception as e:
116
+ print(f"[DataManager] Error syncing data: {e}")
117
+
118
+ def _get_available_spaces(self):
119
+ """获取可用空间列表"""
120
+ available_spaces = set()
121
+ try:
122
+ for filename in os.listdir(LOCAL_DATA_DIR):
123
+ if filename.endswith('_query_log.csv') and '_Student_' in filename:
124
+ space_name = filename.replace('_query_log.csv', '')
125
+ available_spaces.add(space_name)
126
+ return sorted(list(available_spaces))
127
+ except Exception as e:
128
+ print(f"[DataManager] Error getting available spaces: {e}")
129
+ return []
130
+
131
+ def get_available_spaces(self):
132
+ """获取可用空间列表 - 不触发数据同步"""
133
+ if not self.initialized:
134
+ raise Exception("DataManager not initialized")
135
+ return self.available_spaces.copy()
136
+
137
+ def get_repo(self):
138
+ """获取仓库对象"""
139
+ if not self.initialized:
140
+ raise Exception("DataManager not initialized")
141
+ return self.repo
142
+
143
+ def get_repo_dir(self):
144
+ """获取仓库本地目录"""
145
+ if not self.initialized:
146
+ raise Exception("DataManager not initialized")
147
+ return LOCAL_DATA_DIR
148
+
149
+ def stop(self):
150
+ """停止后台同步"""
151
+ if self._sync_thread:
152
+ self._stop_event.set()
153
+ print("[DataManager] Background sync thread stopping...")
154
 
155
  class SpaceActivityCache:
156
+ def __init__(self, data_manager):
157
+ self.data_manager = data_manager
158
+ self.cache_file_path = os.path.join(LOCAL_DATA_DIR, ACTIVITY_CACHE_FILE)
159
  self._cache_data = {}
160
  self._last_update = None
161
  self._update_thread = None
162
  self._stop_event = threading.Event()
 
163
 
164
+ # 启动时立即更新一次缓存
165
+ self._update_cache()
166
 
167
  # 启动后台更新线程
168
  self.start_background_updates()
169
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
170
  def start_background_updates(self):
171
  """启动后台缓存更新线程"""
172
  if self._update_thread and self._update_thread.is_alive():
 
177
  self._update_thread.start()
178
  print("[SpaceActivityCache] Background update thread started")
179
 
180
+ def stop_background_updates(self):
181
+ """停止后台更新线程"""
182
+ if self._update_thread:
183
+ self._stop_event.set()
184
+ print("[SpaceActivityCache] Background update thread stopping...")
185
+
186
  def _background_update_worker(self):
187
+ """后台更新工作线程"""
 
 
 
 
 
 
 
 
 
 
 
188
  while not self._stop_event.is_set():
189
  try:
190
  # 等待指定间隔或停止信号
 
197
 
198
  except Exception as e:
199
  print(f"[SpaceActivityCache] Error in background update: {e}")
200
+ if not self._stop_event.wait(timeout=60): # 1分钟后重试
201
  continue
202
 
203
+ def _update_cache(self):
204
+ """更新缓存数据"""
205
+ try:
206
+ print("[SpaceActivityCache] Updating activity cache...")
207
+ start_time = time.time()
208
+
209
+ # 从数据管理器获取可用空间(不触发数据同步)
210
+ available_spaces = self.data_manager.get_available_spaces()
211
+ repo_dir = self.data_manager.get_repo_dir()
212
+
213
+ new_cache_data = {
214
+ 'last_updated': datetime.now().isoformat(),
215
+ 'update_interval_seconds': CACHE_UPDATE_INTERVAL,
216
+ 'spaces': {}
217
+ }
218
+
219
+ # 为每个空间读取最后活动时间
220
+ for space_name in available_spaces:
221
+ csv_file = os.path.join(repo_dir, f"{space_name}_query_log.csv")
222
+ last_activity, status = self._get_last_activity_from_file(csv_file)
223
+
224
+ new_cache_data['spaces'][space_name] = {
225
+ 'last_activity': last_activity.isoformat() if last_activity != datetime.min else None,
226
+ 'status': status,
227
+ 'cache_update_time': datetime.now().isoformat()
228
+ }
229
+
230
+ print(f"[SpaceActivityCache] {space_name}: {status}")
231
+
232
+ # 线程安全地更新缓存
233
+ with CACHE_LOCK:
234
+ self._cache_data = new_cache_data
235
+ self._last_update = datetime.now()
236
+
237
+ # 保存到文件
238
+ self._save_cache_to_file()
239
+
240
+ elapsed_time = time.time() - start_time
241
+ print(f"[SpaceActivityCache] Cache updated in {elapsed_time:.2f}s, {len(available_spaces)} spaces processed")
242
+
243
+ except Exception as e:
244
+ print(f"[SpaceActivityCache] Error updating cache: {e}")
245
+ import traceback
246
+ print(f"[SpaceActivityCache] Traceback: {traceback.format_exc()}")
247
+
248
+ def _get_last_activity_from_file(self, csv_file_path):
249
+ """从文件读取最后活动时间"""
250
+ try:
251
+ if not os.path.exists(csv_file_path):
252
+ return datetime.min, "file_not_found"
253
+
254
+ # 检查文件大小
255
+ file_size = os.path.getsize(csv_file_path)
256
+ if file_size <= 100:
257
+ return datetime.min, "empty_or_header_only"
258
+
259
+ # 使用CSV reader读取最后一行
260
+ with open(csv_file_path, 'r', encoding='utf-8') as f:
261
+ csv_reader = csv.reader(f)
262
+
263
+ # 跳过header
264
  try:
265
+ header = next(csv_reader)
266
+ except StopIteration:
267
+ return datetime.min, "empty_file"
268
+
269
+ # 读取所有行,获取最后一行
270
+ rows = []
271
+ try:
272
+ for row in csv_reader:
273
+ if row: # 跳过空行
274
+ rows.append(row)
275
+ except Exception as csv_error:
276
+ print(f"[SpaceActivityCache] CSV parsing error for {csv_file_path}: {csv_error}")
277
+ return datetime.min, "csv_parse_error"
278
+
279
+ if not rows:
280
+ return datetime.min, "no_data_rows"
281
 
282
+ # 解析最后一行的时间戳
283
+ last_row = rows[-1]
284
+ if len(last_row) >= 3:
285
+ timestamp_str = last_row[2].strip() # timestamp column
286
+ try:
287
+ parsed_time = datetime.strptime(timestamp_str, '%Y-%m-%d %H:%M:%S')
288
+ return parsed_time, f"active_last_at_{parsed_time.strftime('%Y-%m-%d_%H:%M:%S')}"
289
+ except ValueError as ve:
290
+ print(f"[SpaceActivityCache] Date parsing error for '{timestamp_str}': {ve}")
291
+ return datetime.min, "date_parse_error"
292
+ else:
293
+ return datetime.min, "invalid_row_format"
294
+
295
+ except Exception as e:
296
+ print(f"[SpaceActivityCache] Error reading {csv_file_path}: {e}")
297
+ return datetime.min, "read_error"
298
+
299
+ def _save_cache_to_file(self):
300
+ """保存缓存到文件"""
301
+ try:
302
+ with open(self.cache_file_path, 'w', encoding='utf-8') as f:
303
+ json.dump(self._cache_data, f, indent=2, ensure_ascii=False)
304
+ print(f"[SpaceActivityCache] Cache saved to {self.cache_file_path}")
305
+ except Exception as e:
306
+ print(f"[SpaceActivityCache] Error saving cache to file: {e}")
307
+
308
+ def _load_cache_from_file(self):
309
+ """从文件加载缓存"""
310
+ try:
311
+ if os.path.exists(self.cache_file_path):
312
+ with open(self.cache_file_path, 'r', encoding='utf-8') as f:
313
+ data = json.load(f)
314
+ print(f"[SpaceActivityCache] Cache loaded from file")
315
+ return data
316
+ except Exception as e:
317
+ print(f"[SpaceActivityCache] Error loading cache from file: {e}")
318
+ return {}
319
+
320
+ def get_space_activity(self, space_name):
321
+ """获取指定空间的活动信息"""
322
+ with CACHE_LOCK:
323
  if not self._cache_data or 'spaces' not in self._cache_data:
324
+ # 缓存为空,尝试从文件加载
325
+ self._cache_data = self._load_cache_from_file()
326
+ if not self._cache_data:
327
+ # 文件也没有,立即更新一次
328
+ print("[SpaceActivityCache] No cache available, updating immediately...")
329
+ self._update_cache()
330
 
331
  spaces_data = self._cache_data.get('spaces', {})
332
  space_info = spaces_data.get(space_name, {})
 
348
  return last_activity, status
349
 
350
  def get_all_spaces_activity(self):
351
+ """获取所有空间的活动信息"""
352
  with CACHE_LOCK:
353
+ if not self._cache_data or 'spaces' not in self._cache_data:
354
+ self._cache_data = self._load_cache_from_file()
355
+ if not self._cache_data:
356
+ print("[SpaceActivityCache] No cache available, updating immediately...")
357
+ self._update_cache()
 
 
 
358
 
359
  result = {}
360
  spaces_data = self._cache_data.get('spaces', {})
361
 
 
 
 
 
 
362
  for space_name, space_info in spaces_data.items():
363
  last_activity_str = space_info.get('last_activity')
364
  if last_activity_str:
 
388
  'last_updated': last_updated,
389
  'age_minutes': age_minutes,
390
  'spaces_count': len(self._cache_data.get('spaces', {})),
391
+ 'is_fresh': age_minutes < (CACHE_UPDATE_INTERVAL / 60) * 1.5
392
  }
393
  except:
394
  pass
 
405
  print("[SpaceActivityCache] Force updating cache...")
406
  self._update_cache()
407
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
408
  # 新增:本地防撞车机制
409
  class LocalAllocationTracker:
410
  def __init__(self):
 
480
  return summary
481
 
482
  # 全局实例
483
+ data_manager = DataManager()
484
+ activity_cache = None
485
  local_tracker = LocalAllocationTracker()
486
 
487
+ def init_system():
488
+ """初始化整个系统 - 只在启动时调用一次"""
489
+ global activity_cache
490
+
491
+ print("[init_system] Initializing load distributor system...")
492
+
493
+ # 初始化数据管理器
494
+ if not data_manager.initialize():
495
+ raise Exception("Failed to initialize data manager")
496
+
497
+ # 初始化活动缓存
498
+ activity_cache = SpaceActivityCache(data_manager)
499
+
500
+ print("[init_system] System initialization completed")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
501
 
502
+ def analyze_space_activity_cached():
503
+ """使用缓存的空间活动分析 - 不再需要传入参数"""
504
+ global activity_cache
505
 
506
+ if activity_cache is None:
507
+ raise Exception("Activity cache not initialized")
508
+
509
+ # 从数据管理器获取可用空间列表(不触发数据同步)
510
+ available_spaces = data_manager.get_available_spaces()
511
+ repo_dir = data_manager.get_repo_dir()
512
+
513
+ space_activity = []
514
+ current_time = datetime.now()
515
+
516
+ # 获取缓存信息
517
+ cache_info = activity_cache.get_cache_info()
518
+ print(f"[analyze_space_activity_cached] Using cache (age: {cache_info['age_minutes']:.1f} min, fresh: {cache_info['is_fresh']})")
519
+
520
+ # 获取所有空间的缓存活动数据
521
+ all_spaces_activity = activity_cache.get_all_spaces_activity()
522
+
523
+ # Read allocation records to filter out recently allocated spaces
524
+ active_allocations = read_allocation_records(repo_dir)
525
+
526
+ print(f"[analyze_space_activity_cached] Analyzing {len(available_spaces)} spaces using cached data...")
527
+
528
+ for space_name in available_spaces:
529
+ # 从缓存获取活动信息
530
+ cached_info = all_spaces_activity.get(space_name)
531
 
532
+ if cached_info:
533
+ last_activity = cached_info['last_activity']
534
+ cached_status = cached_info['status']
535
+ else:
536
+ # 缓存中没有这个空间,可能是新空间
537
+ last_activity = datetime.min
538
+ cached_status = "not_in_cache"
539
 
540
+ # Calculate idle time in minutes
541
+ if last_activity == datetime.min or 'empty' in cached_status or 'not_found' in cached_status:
542
+ idle_minutes = float('inf') # Never used
543
+ status = "Never used"
544
+ last_activity_str = "Never"
545
+ else:
546
+ idle_minutes = (current_time - last_activity).total_seconds() / 60
547
+ status = f"Idle for {idle_minutes:.1f} minutes (cached)"
548
+ last_activity_str = last_activity.strftime('%Y-%m-%d %H:%M:%S')
 
549
 
550
+ # Check if space is recently allocated (remote)
551
+ is_recently_allocated_remote = space_name in active_allocations
552
+ if is_recently_allocated_remote:
553
+ alloc_info = active_allocations[space_name]
554
+ minutes_until_free = (alloc_info['expires_at'] - current_time).total_seconds() / 60
555
+ status += f" (Recently allocated remotely, free in {minutes_until_free:.1f} min)"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
556
 
557
+ # Check if space is recently allocated (local)
558
+ is_recently_allocated_local, local_student = local_tracker.is_recently_allocated_locally(space_name)
559
+ if is_recently_allocated_local:
560
+ status += f" (Recently allocated locally to {local_student})"
561
 
562
+ space_activity.append({
563
+ 'space_name': space_name,
564
+ 'last_activity': last_activity,
565
+ 'last_activity_str': last_activity_str,
566
+ 'idle_minutes': idle_minutes,
567
+ 'status': status,
568
+ 'is_recently_allocated_remote': is_recently_allocated_remote,
569
+ 'is_recently_allocated_local': is_recently_allocated_local,
570
+ 'cached_status': cached_status
571
+ })
572
 
573
+ print(f"[analyze_space_activity_cached] {space_name}: {status}")
574
+
575
+ # Sort by idle time (most idle first)
576
+ space_activity.sort(key=lambda x: x['idle_minutes'], reverse=True)
577
+
578
+ return space_activity
 
 
 
 
 
 
 
 
 
 
 
 
 
579
 
580
  def read_allocation_records(repo_dir):
581
  """Read and parse allocation records, returning only non-expired allocations"""
 
719
  print(f"[simple_push_allocation_record] Allocation recorded locally, sync will happen eventually")
720
  return False
721
 
722
+ def create_status_display(space_activity):
723
+ """Create formatted status display for all spaces with proper line breaks"""
 
 
 
 
 
 
 
 
724
  status_display = "📊 **Current Space Status (sorted by availability):**<br><br>"
725
 
 
 
 
 
 
 
 
 
 
 
 
 
726
  # 显示本地分配记录摘要
727
  local_summary = local_tracker.get_recent_allocations_summary()
728
  if local_summary:
 
734
  for i, space in enumerate(space_activity, 1):
735
  status_display += f"{i}. **{space['space_name']}**<br>"
736
  status_display += f"&nbsp;&nbsp;&nbsp;• Status: {space['status']}<br>"
737
+ status_display += f"&nbsp;&nbsp;&nbsp;• Last activity: {space['last_activity_str']}<br><br>"
 
 
 
 
 
 
738
 
739
  return status_display
740
 
741
+ def select_space_with_enhanced_collision_avoidance_cached(space_activity, student_id):
 
742
  """使用缓存的增强防撞空间选择函数"""
743
 
744
  print(f"[select_space_with_enhanced_collision_avoidance_cached] Starting selection for student: {student_id}")
 
810
  print(f"[select_space_with_enhanced_collision_avoidance_cached] Local allocation recorded BEFORE file write")
811
 
812
  # 第五步:记录到文件和远程
813
+ repo = data_manager.get_repo()
814
+ repo_dir = data_manager.get_repo_dir()
815
+
816
+ write_success = write_allocation_record(space_name, student_id, repo_dir)
817
  if write_success:
818
  push_success = simple_push_allocation_record(repo, space_name, student_id)
819
  if not push_success:
 
829
 
830
  return redirect_to_space(redirect_url, selected_space, status_display)
831
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
832
  def redirect_to_space(redirect_url, selected_space, status_display):
833
  """Display redirect information with manual click option"""
834
 
 
891
  return gr.HTML(redirect_html)
892
 
893
  def load_balance_user_cached(student_id):
894
+ """使用缓存的负载均衡函数 - 不再初始化数据连接"""
895
  print(f"[load_balance_user_cached] Starting cached load balancing for student ID: {student_id}")
896
 
897
+ # 检查系统是否已初始化
898
+ if not data_manager.initialized:
899
+ raise gr.Error("🚫 System not properly initialized. Please contact administrator.", duration=8)
 
 
 
 
 
 
 
900
 
901
+ # 使用缓存版本的分析函数(不触发数据同步)
902
+ space_activity = analyze_space_activity_cached()
 
 
 
903
 
904
  # Select space with enhanced collision avoidance
905
+ return select_space_with_enhanced_collision_avoidance_cached(space_activity, student_id)
906
 
907
  def get_url_params(request: gr.Request):
908
  """Extract URL parameters from request"""
 
991
  """
992
  return title, gr.HTML(error_html)
993
 
994
+ # 管理功能
995
+ def get_cache_status():
996
+ """获取缓存状态(用于调试或管理)"""
997
+ global activity_cache
998
+ if activity_cache:
999
+ return activity_cache.get_cache_info()
1000
+ return {"error": "Cache not initialized"}
1001
+
1002
+ def force_cache_update():
1003
+ """强制更新缓存(用于调试或管理)"""
1004
+ global activity_cache
1005
+ if activity_cache:
1006
+ activity_cache.force_update()
1007
+ return {"status": "Cache updated"}
1008
+ return {"error": "Cache not initialized"}
1009
+
1010
+ def get_data_manager_status():
1011
+ """获取数据管理器状态"""
1012
+ return {
1013
+ "initialized": data_manager.initialized,
1014
+ "last_sync": data_manager.last_data_sync.isoformat() if data_manager.last_data_sync else None,
1015
+ "spaces_count": len(data_manager.available_spaces),
1016
+ "repo_connected": data_manager.repo is not None
1017
+ }
1018
+
1019
+ def force_data_sync():
1020
+ """强制数据同步"""
1021
+ try:
1022
+ data_manager._sync_data()
1023
+ return {"status": "Data sync completed"}
1024
+ except Exception as e:
1025
+ return {"error": str(e)}
1026
+
1027
  # 添加可选的管理界面(用于调试)
1028
  def create_admin_interface():
1029
+ """创建管理界面(可选)- 增强版本"""
1030
  with gr.Blocks(title="CIV3283 Load Distributor - Admin") as admin_interface:
1031
  gr.Markdown("# 🔧 CIV3283 Load Distributor - Admin Panel")
1032
 
1033
+ with gr.Tab("Cache Management"):
1034
+ with gr.Row():
1035
+ cache_status_btn = gr.Button("📊 Check Cache Status", variant="secondary")
1036
+ force_cache_update_btn = gr.Button("🔄 Force Cache Update", variant="primary")
1037
+
1038
+ cache_info_display = gr.JSON(label="Cache Information")
1039
+ cache_status_message = gr.Markdown("")
1040
+
1041
+ with gr.Tab("Data Management"):
1042
+ with gr.Row():
1043
+ data_status_btn = gr.Button("📊 Check Data Manager Status", variant="secondary")
1044
+ force_data_sync_btn = gr.Button("🔄 Force Data Sync", variant="primary")
1045
+
1046
+ data_info_display = gr.JSON(label="Data Manager Information")
1047
+ data_status_message = gr.Markdown("")
1048
 
1049
  def check_cache_status():
1050
  try:
 
1077
  except Exception as e:
1078
  return {"error": str(e)}, f"❌ Error updating cache: {str(e)}"
1079
 
1080
+ def check_data_status():
1081
+ try:
1082
+ status = get_data_manager_status()
1083
+ if status['initialized']:
1084
+ last_sync = status['last_sync']
1085
+ if last_sync:
1086
+ sync_time = datetime.fromisoformat(last_sync)
1087
+ age_minutes = (datetime.now() - sync_time).total_seconds() / 60
1088
+ message = f"✅ Data manager active (last sync: {age_minutes:.1f} min ago, {status['spaces_count']} spaces)"
1089
+ else:
1090
+ message = "⚠️ Data manager initialized but no sync recorded"
1091
+ else:
1092
+ message = "❌ Data manager not initialized"
1093
+ return status, message
1094
+ except Exception as e:
1095
+ return {"error": str(e)}, f"❌ Error checking data manager: {str(e)}"
1096
+
1097
+ def force_sync():
1098
+ try:
1099
+ result = force_data_sync()
1100
+ if 'error' in result:
1101
+ return result, "❌ Failed to sync data"
1102
+ else:
1103
+ # 获取更新后的状态
1104
+ status = get_data_manager_status()
1105
+ return status, "✅ Data sync completed successfully"
1106
+ except Exception as e:
1107
+ return {"error": str(e)}, f"❌ Error syncing data: {str(e)}"
1108
+
1109
+ # Cache tab event handlers
1110
  cache_status_btn.click(
1111
  fn=check_cache_status,
1112
+ outputs=[cache_info_display, cache_status_message]
1113
  )
1114
 
1115
+ force_cache_update_btn.click(
1116
  fn=force_update,
1117
+ outputs=[cache_info_display, cache_status_message]
1118
+ )
1119
+
1120
+ # Data tab event handlers
1121
+ data_status_btn.click(
1122
+ fn=check_data_status,
1123
+ outputs=[data_info_display, data_status_message]
1124
+ )
1125
+
1126
+ force_data_sync_btn.click(
1127
+ fn=force_sync,
1128
+ outputs=[data_info_display, data_status_message]
1129
  )
1130
 
1131
  return admin_interface
 
1149
  if __name__ == "__main__":
1150
  import sys
1151
 
1152
+ try:
1153
+ # 系统初始化 - 只在启动时执行一次
1154
+ print("🚀 Initializing CIV3283 Load Distributor System...")
1155
+ init_system()
1156
+ print(" System initialization completed successfully")
 
 
 
1157
 
1158
+ # 检查是否需要管理界面
1159
+ enable_admin = "--admin" in sys.argv or os.environ.get("ENABLE_ADMIN", "false").lower() == "true"
1160
+
1161
+ if enable_admin:
1162
+ print("🔧 Starting with admin interface enabled")
1163
+ # 创建带管理界面的组合界面
1164
+ main_interface = create_main_interface()
1165
+ admin_interface = create_admin_interface()
1166
+
1167
+ # 使用TabbedInterface组合两个界面
1168
+ combined_interface = gr.TabbedInterface(
1169
+ [main_interface, admin_interface],
1170
+ ["🎯 Load Distributor", "🔧 Admin Panel"],
1171
+ title="CIV3283 Load Distributor System"
1172
+ )
1173
+ combined_interface.launch()
1174
+ else:
1175
+ print("🎯 Starting main interface only")
1176
+ # 只启动主界面
1177
+ main_interface = create_main_interface()
1178
+ main_interface.launch()
1179
+
1180
+ except Exception as e:
1181
+ print(f"❌ System initialization failed: {e}")
1182
+ import traceback
1183
+ print(f"Traceback: {traceback.format_exc()}")
1184
+ raise