朱东升 commited on
Commit
ccdd995
·
1 Parent(s): d4652ff
Files changed (1) hide show
  1. app.py +208 -170
app.py CHANGED
@@ -10,7 +10,9 @@ import time
10
  import threading
11
  import queue
12
  import uuid
 
13
  from datetime import datetime
 
14
  from src.containerized_eval import eval_string_script
15
 
16
  # 添加当前目录和src目录到模块搜索路径
@@ -25,7 +27,7 @@ if src_dir not in sys.path:
25
  task_queue = queue.Queue()
26
  # 存储任务状态的字典
27
  task_status = {}
28
- # 存储任务历史的列表,最多保存最近10个任务
29
  task_history = []
30
  # 用于保护共享资源的锁
31
  lock = threading.Lock()
@@ -33,6 +35,8 @@ lock = threading.Lock()
33
  worker_threads = multiprocessing.cpu_count()
34
  # 后台线程是否运行的标志
35
  running = True
 
 
36
 
37
  def queue_processor():
38
  """处理队列中的任务"""
@@ -44,6 +48,21 @@ def queue_processor():
44
  task_status[task_id]['status'] = 'processing'
45
  task_status[task_id]['start_time'] = time.time()
46
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
47
  # 处理任务
48
  result = evaluate(input_data)
49
 
@@ -57,15 +76,30 @@ def queue_processor():
57
  task_status[task_id]['end_time'] = end_time
58
  task_status[task_id]['process_time'] = process_time
59
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
60
  # 更新任务历史
61
  task_history.append({
62
  'task_id': task_id,
63
  'request_time': request_time,
64
  'process_time': process_time,
65
- 'status': 'completed'
 
66
  })
67
- # 只保留最近10个任务
68
- while len(task_history) > 10:
69
  task_history.pop(0)
70
 
71
  # 标记任务完成
@@ -82,6 +116,44 @@ def queue_processor():
82
  task_status[task_id]['end_time'] = time.time()
83
  task_queue.task_done()
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
  def evaluate(input_data):
86
  """评估代码的主函数
87
 
@@ -174,6 +246,21 @@ def synchronous_evaluate(input_data):
174
  Returns:
175
  dict: 评估结果
176
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
177
  # 获取队列当前状态
178
  queue_info = get_queue_status()
179
  waiting_tasks = queue_info['waiting_tasks']
@@ -188,7 +275,13 @@ def synchronous_evaluate(input_data):
188
  'status': 'queued',
189
  'queued_time': request_time,
190
  'queue_position': task_queue.qsize() + 1,
191
- 'synchronous': True # 标记为同步任务
 
 
 
 
 
 
192
  }
193
 
194
  # 将任务添加到队列
@@ -213,6 +306,30 @@ def synchronous_evaluate(input_data):
213
  # 短暂睡眠避免CPU占用过高
214
  time.sleep(0.1)
215
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
216
  def enqueue_task(input_data):
217
  """将任务添加到队列
218
 
@@ -222,6 +339,20 @@ def enqueue_task(input_data):
222
  Returns:
223
  dict: 包含任务ID和状态的字典
224
  """
 
 
 
 
 
 
 
 
 
 
 
 
 
 
225
  task_id = str(uuid.uuid4())
226
  request_time = time.time()
227
 
@@ -230,9 +361,19 @@ def enqueue_task(input_data):
230
  task_status[task_id] = {
231
  'status': 'queued',
232
  'queued_time': request_time,
233
- 'queue_position': task_queue.qsize() + 1
 
 
 
 
 
 
234
  }
235
 
 
 
 
 
236
  # 将任务添加到队列
237
  task_queue.put((task_id, input_data, request_time))
238
 
@@ -240,7 +381,8 @@ def enqueue_task(input_data):
240
  'task_id': task_id,
241
  'status': 'queued',
242
  'queue_position': task_status[task_id]['queue_position'],
243
- 'estimated_wait': task_status[task_id]['queue_position'] * 5 # 估计等待时间,假设每个任务平均5秒
 
244
  }
245
 
246
  def check_status(task_id):
@@ -271,32 +413,70 @@ def get_queue_status():
271
  dict: 包含队列状态的字典
272
  """
273
  with lock:
 
 
 
 
274
  queue_size = task_queue.qsize()
275
- active_tasks = sum(1 for status in task_status.values() if status['status'] == 'processing')
276
- waiting_tasks = sum(1 for status in task_status.values() if status['status'] == 'queued')
277
 
278
- recent_tasks = task_history[-5:] if task_history else []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
279
 
280
- # 计算平均处理时间,如果没有历史数据,使用默认值
281
- if recent_tasks:
282
- avg_time = sum(task['process_time'] for task in recent_tasks) / len(recent_tasks)
283
- # 确保平均时间至少为1秒
284
- avg_time = max(avg_time, 1.0)
285
- else:
286
- # 没有历史数据时的默认平均处理时间(秒)
287
- avg_time = 5.0
288
 
289
- # 估算等待时间
290
- # 如果有活跃任务,假设它们还需要完成一半的平均时间
291
- active_time = (active_tasks * avg_time) / 2 if active_tasks > 0 else 0
292
- # 排队任务的总时间
293
- queue_time = waiting_tasks * avg_time
294
- # 总预计等待时间
295
- estimated_wait = active_time + queue_time
296
 
297
- # 确保即使没有任务也返回有效值
298
- if estimated_wait == 0 and (waiting_tasks > 0 or active_tasks > 0):
299
- estimated_wait = 1.0
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
300
 
301
  return {
302
  'queue_size': queue_size,
@@ -304,7 +484,6 @@ def get_queue_status():
304
  'waiting_tasks': waiting_tasks,
305
  'worker_threads': worker_threads,
306
  'estimated_wait': estimated_wait,
307
- 'avg_process_time': avg_time,
308
  'recent_tasks': recent_tasks
309
  }
310
 
@@ -328,113 +507,6 @@ def format_time(seconds):
328
  minutes = int((seconds % 3600) / 60)
329
  return f"{hours}小时{minutes}分钟"
330
 
331
- def ui_submit(input_json):
332
- """提交任务到队列的UI函数
333
-
334
- Args:
335
- input_json: JSON格式的输入数据
336
-
337
- Returns:
338
- tuple: 任务ID和初始状态信息
339
- """
340
- try:
341
- input_data = json.loads(input_json) if isinstance(input_json, str) else input_json
342
- response = enqueue_task(input_data)
343
- task_id = response['task_id']
344
-
345
- status_html = f"""
346
- <div class="status-card">
347
- <h3>任务状态</h3>
348
- <p><b>任务ID:</b> {task_id}</p>
349
- <p><b>状态:</b> 已入队</p>
350
- <p><b>队列位置:</b> {response['queue_position']}</p>
351
- <p><b>预计等待时间:</b> {format_time(response['estimated_wait'])}</p>
352
- </div>
353
- """
354
-
355
- return task_id, status_html
356
- except Exception as e:
357
- return None, f"<div class='error-message'>提交失败: {str(e)}</div>"
358
-
359
- def ui_check_status(task_id):
360
- """检查任务状态的UI函数
361
-
362
- Args:
363
- task_id: 任务ID
364
-
365
- Returns:
366
- str: 包含任务状态的HTML
367
- """
368
- if not task_id:
369
- return "<div class='notice'>请提供任务ID</div>"
370
-
371
- status = check_status(task_id)
372
-
373
- if status['status'] == 'not_found':
374
- return "<div class='error-message'>任务未找到</div>"
375
-
376
- if status['status'] == 'queued':
377
- queue_info = get_queue_status()
378
-
379
- # 计算这个特定任务的等待时间
380
- avg_time = queue_info.get('avg_process_time', 5.0)
381
- active_time = (queue_info['active_tasks'] * avg_time) / 2 if queue_info['active_tasks'] > 0 else 0
382
-
383
- # 计算此任务前面的任务数
384
- tasks_ahead = status['queue_position'] - 1
385
-
386
- # 此任务的估计等待时间 = 活跃任务完成时间 + 前面排队任务时间
387
- est_wait = active_time + (tasks_ahead * avg_time)
388
-
389
- return f"""
390
- <div class="status-card">
391
- <h3>任务状态</h3>
392
- <p><b>任务ID:</b> {task_id}</p>
393
- <p><b>状态:</b> 等待中</p>
394
- <p><b>队列位置:</b> {status['queue_position']}</p>
395
- <p><b>入队时间:</b> {datetime.fromtimestamp(status['queued_time']).strftime('%H:%M:%S')}</p>
396
- <p><b>预计等待时间:</b> {format_time(est_wait)}</p>
397
- </div>
398
- """
399
-
400
- if status['status'] == 'processing':
401
- process_time = time.time() - status['start_time']
402
-
403
- return f"""
404
- <div class="status-card">
405
- <h3>任务状态</h3>
406
- <p><b>任务ID:</b> {task_id}</p>
407
- <p><b>状态:</b> 处理中</p>
408
- <p><b>已处理时间:</b> {format_time(process_time)}</p>
409
- <p><b>入队时间:</b> {datetime.fromtimestamp(status['queued_time']).strftime('%H:%M:%S')}</p>
410
- </div>
411
- """
412
-
413
- if status['status'] == 'completed':
414
- result = status.get('result', {})
415
-
416
- return f"""
417
- <div class="status-card success">
418
- <h3>任务状态</h3>
419
- <p><b>任务ID:</b> {task_id}</p>
420
- <p><b>状态:</b> 已完成</p>
421
- <p><b>处理时间:</b> {format_time(status['process_time'])}</p>
422
- <p><b>完成时间:</b> {datetime.fromtimestamp(status['end_time']).strftime('%H:%M:%S')}</p>
423
- </div>
424
- """
425
-
426
- if status['status'] == 'error':
427
- return f"""
428
- <div class="status-card error">
429
- <h3>任务状态</h3>
430
- <p><b>任务ID:</b> {task_id}</p>
431
- <p><b>状态:</b> 错误</p>
432
- <p><b>错误信息:</b> {status.get('error', '未知错误')}</p>
433
- </div>
434
- """
435
-
436
- return "<div class='error-message'>未知状态</div>"
437
-
438
  def ui_get_queue_info():
439
  """获取队列信息的UI函数
440
 
@@ -461,9 +533,6 @@ def ui_get_queue_info():
461
  </tr>
462
  """
463
 
464
- # 添加平均处理时间显示
465
- avg_time_html = f"""<p><b>平均处理时间:</b> {format_time(queue_info.get('avg_process_time', 0))}</p>"""
466
-
467
  return f"""
468
  <div class="dashboard">
469
  <div class="queue-info-card main-card">
@@ -484,7 +553,6 @@ def ui_get_queue_info():
484
  </div>
485
 
486
  <div class="wait-time">
487
- {avg_time_html}
488
  <p><b>当前预计等待时间:</b> {format_time(queue_info['estimated_wait'])}</p>
489
  <p class="last-update"><small>最后更新: {datetime.now().strftime('%H:%M:%S')}</small></p>
490
  </div>
@@ -508,36 +576,6 @@ def ui_get_queue_info():
508
  </div>
509
  """
510
 
511
- def ui_get_result(task_id):
512
- """获取任务结果的UI函数
513
-
514
- Args:
515
- task_id: 任务ID
516
-
517
- Returns:
518
- 任务结果
519
- """
520
- if not task_id:
521
- return None
522
-
523
- status = check_status(task_id)
524
-
525
- if status['status'] == 'completed':
526
- return status.get('result', None)
527
-
528
- return None
529
-
530
- def launch_workers():
531
- """启动工作线程"""
532
- global running
533
- running = True
534
-
535
- # 创建工作线程
536
- for _ in range(worker_threads):
537
- worker = threading.Thread(target=queue_processor)
538
- worker.daemon = True
539
- worker.start()
540
-
541
  # 自定义CSS
542
  custom_css = """
543
  .container {
 
10
  import threading
11
  import queue
12
  import uuid
13
+ import numpy as np
14
  from datetime import datetime
15
+ from tqdm.auto import tqdm
16
  from src.containerized_eval import eval_string_script
17
 
18
  # 添加当前目录和src目录到模块搜索路径
 
27
  task_queue = queue.Queue()
28
  # 存储任务状态的字典
29
  task_status = {}
30
+ # 存储任务历史的列表,最多保存最近20个任务
31
  task_history = []
32
  # 用于保护共享资源的锁
33
  lock = threading.Lock()
 
35
  worker_threads = multiprocessing.cpu_count()
36
  # 后台线程是否运行的标志
37
  running = True
38
+ # 任务类型到处理时间的映射
39
+ task_type_times = {}
40
 
41
  def queue_processor():
42
  """处理队列中的任务"""
 
48
  task_status[task_id]['status'] = 'processing'
49
  task_status[task_id]['start_time'] = time.time()
50
 
51
+ # 识别任务特征以估计完成时间
52
+ # 例如:语言类型、代码大小等
53
+ if isinstance(input_data, list) and len(input_data) > 0:
54
+ sample_task = input_data[0]
55
+ language = sample_task.get('language', 'unknown') if isinstance(sample_task, dict) else 'unknown'
56
+ task_size = len(input_data)
57
+ task_complexity = _estimate_task_complexity(input_data)
58
+
59
+ with lock:
60
+ task_status[task_id]['estimated_factors'] = {
61
+ 'language': language,
62
+ 'size': task_size,
63
+ 'complexity': task_complexity
64
+ }
65
+
66
  # 处理任务
67
  result = evaluate(input_data)
68
 
 
76
  task_status[task_id]['end_time'] = end_time
77
  task_status[task_id]['process_time'] = process_time
78
 
79
+ # 更新任务类型到处理时间的映射
80
+ if 'estimated_factors' in task_status[task_id]:
81
+ factors = task_status[task_id]['estimated_factors']
82
+ key = f"{factors['language']}_{factors['complexity']}"
83
+
84
+ if key not in task_type_times:
85
+ task_type_times[key] = []
86
+
87
+ # 记录此类型任务的处理时间
88
+ task_type_times[key].append(process_time / factors['size'])
89
+ # 只保留最近的10个记录
90
+ if len(task_type_times[key]) > 10:
91
+ task_type_times[key] = task_type_times[key][-10:]
92
+
93
  # 更新任务历史
94
  task_history.append({
95
  'task_id': task_id,
96
  'request_time': request_time,
97
  'process_time': process_time,
98
+ 'status': 'completed',
99
+ 'factors': task_status[task_id].get('estimated_factors', {})
100
  })
101
+ # 只保留最近20个任务
102
+ while len(task_history) > 20:
103
  task_history.pop(0)
104
 
105
  # 标记任务完成
 
116
  task_status[task_id]['end_time'] = time.time()
117
  task_queue.task_done()
118
 
119
+ def _estimate_task_complexity(tasks):
120
+ """估计任务复杂度
121
+
122
+ Args:
123
+ tasks: 任务列表
124
+
125
+ Returns:
126
+ str: 复杂度评级 ('simple', 'medium', 'complex')
127
+ """
128
+ # 基于代码和测试的长度评估复杂度
129
+ total_code_length = 0
130
+ count = 0
131
+
132
+ for task in tasks:
133
+ if isinstance(task, dict):
134
+ prompt = task.get('prompt', '')
135
+ tests = task.get('tests', '')
136
+ completions = task.get('processed_completions', [])
137
+
138
+ code_length = len(prompt) + len(tests)
139
+ if completions:
140
+ code_length += sum(len(comp) for comp in completions)
141
+
142
+ total_code_length += code_length
143
+ count += 1
144
+
145
+ if count == 0:
146
+ return 'medium' # 默认中等复杂度
147
+
148
+ avg_length = total_code_length / count
149
+
150
+ if avg_length < 1000:
151
+ return 'simple'
152
+ elif avg_length < 5000:
153
+ return 'medium'
154
+ else:
155
+ return 'complex'
156
+
157
  def evaluate(input_data):
158
  """评估代码的主函数
159
 
 
246
  Returns:
247
  dict: 评估结果
248
  """
249
+ # a) 估计此任务的特征
250
+ if isinstance(input_data, list) and len(input_data) > 0:
251
+ sample_task = input_data[0]
252
+ language = sample_task.get('language', 'unknown') if isinstance(sample_task, dict) else 'unknown'
253
+ task_size = len(input_data)
254
+ task_complexity = _estimate_task_complexity(input_data)
255
+ else:
256
+ language = 'unknown'
257
+ task_size = 1
258
+ task_complexity = 'medium'
259
+
260
+ # b) 估计完成时间用于前端显示
261
+ estimated_time_per_task = _get_estimated_time_for_task(language, task_complexity)
262
+ estimated_total_time = estimated_time_per_task * task_size
263
+
264
  # 获取队列当前状态
265
  queue_info = get_queue_status()
266
  waiting_tasks = queue_info['waiting_tasks']
 
275
  'status': 'queued',
276
  'queued_time': request_time,
277
  'queue_position': task_queue.qsize() + 1,
278
+ 'synchronous': True, # 标记为同步任务
279
+ 'estimated_factors': {
280
+ 'language': language,
281
+ 'size': task_size,
282
+ 'complexity': task_complexity
283
+ },
284
+ 'estimated_time': estimated_total_time
285
  }
286
 
287
  # 将任务添加到队列
 
306
  # 短暂睡眠避免CPU占用过高
307
  time.sleep(0.1)
308
 
309
+ def _get_estimated_time_for_task(language, complexity):
310
+ """获取特定类型任务的估计处理时间
311
+
312
+ Args:
313
+ language: 编程语言
314
+ complexity: 任务复杂度
315
+
316
+ Returns:
317
+ float: 估计的处理时间(秒)
318
+ """
319
+ key = f"{language}_{complexity}"
320
+
321
+ # 如果有历史数据,使用中位数作为估计值
322
+ if key in task_type_times and len(task_type_times[key]) > 0:
323
+ return np.median(task_type_times[key])
324
+
325
+ # 否则使用基于复杂度的默认估计值
326
+ if complexity == 'simple':
327
+ return 1.0
328
+ elif complexity == 'medium':
329
+ return 3.0
330
+ else: # complex
331
+ return 8.0
332
+
333
  def enqueue_task(input_data):
334
  """将任务添加到队列
335
 
 
339
  Returns:
340
  dict: 包含任务ID和状态的字典
341
  """
342
+ # 估计任务特征和处理时间
343
+ if isinstance(input_data, list) and len(input_data) > 0:
344
+ sample_task = input_data[0]
345
+ language = sample_task.get('language', 'unknown') if isinstance(sample_task, dict) else 'unknown'
346
+ task_size = len(input_data)
347
+ task_complexity = _estimate_task_complexity(input_data)
348
+ else:
349
+ language = 'unknown'
350
+ task_size = 1
351
+ task_complexity = 'medium'
352
+
353
+ estimated_time_per_task = _get_estimated_time_for_task(language, task_complexity)
354
+ estimated_total_time = estimated_time_per_task * task_size
355
+
356
  task_id = str(uuid.uuid4())
357
  request_time = time.time()
358
 
 
361
  task_status[task_id] = {
362
  'status': 'queued',
363
  'queued_time': request_time,
364
+ 'queue_position': task_queue.qsize() + 1,
365
+ 'estimated_factors': {
366
+ 'language': language,
367
+ 'size': task_size,
368
+ 'complexity': task_complexity
369
+ },
370
+ 'estimated_time': estimated_total_time
371
  }
372
 
373
+ # 获取队列状态以计算等待时间
374
+ queue_info = get_queue_status()
375
+ est_wait = queue_info['estimated_wait']
376
+
377
  # 将任务添加到队列
378
  task_queue.put((task_id, input_data, request_time))
379
 
 
381
  'task_id': task_id,
382
  'status': 'queued',
383
  'queue_position': task_status[task_id]['queue_position'],
384
+ 'estimated_wait': est_wait,
385
+ 'estimated_processing': estimated_total_time
386
  }
387
 
388
  def check_status(task_id):
 
413
  dict: 包含队列状态的字典
414
  """
415
  with lock:
416
+ # 获取队列中的所有任务
417
+ queued_tasks = [t for t in task_status.values() if t['status'] == 'queued']
418
+ processing_tasks = [t for t in task_status.values() if t['status'] == 'processing']
419
+
420
  queue_size = task_queue.qsize()
421
+ active_tasks = len(processing_tasks)
422
+ waiting_tasks = len(queued_tasks)
423
 
424
+ # 更准确地估计等待时间
425
+ # 1. 计算当前处理中任务的剩余时间
426
+ remaining_processing_time = 0
427
+ for task in processing_tasks:
428
+ # 如果任务有开始时间和估计总时间
429
+ if 'start_time' in task and 'estimated_time' in task:
430
+ elapsed = time.time() - task['start_time']
431
+ # 剩余时间 = 估计总时间 - 已经过去的时间
432
+ remaining = max(0, task['estimated_time'] - elapsed)
433
+ remaining_processing_time += remaining
434
+ else:
435
+ # 默认假设还需要2秒
436
+ remaining_processing_time += 2
437
+
438
+ # 使用动态均衡:根据工作线程数量平衡负载
439
+ if active_tasks > 0:
440
+ remaining_processing_time = remaining_processing_time / min(active_tasks, worker_threads)
441
+
442
+ # 2. 计算排队中任务的估计处理时间
443
+ queued_processing_time = 0
444
+ for task in queued_tasks:
445
+ if 'estimated_time' in task:
446
+ queued_processing_time += task['estimated_time']
447
+ else:
448
+ # 默认假设每个任务5秒
449
+ queued_processing_time += 5
450
 
451
+ # 考虑并行处理:分摊到可用工作线程
452
+ if worker_threads > 0 and queued_processing_time > 0:
453
+ queued_processing_time = queued_processing_time / worker_threads
 
 
 
 
 
454
 
455
+ # 总估计等待时间
456
+ estimated_wait = remaining_processing_time + queued_processing_time
 
 
 
 
 
457
 
458
+ # 应用统计校正:使用历史数据调整预测
459
+ if task_history:
460
+ # 计算历史预测与实际处理时间的比例
461
+ prediction_ratios = []
462
+ for task in task_history:
463
+ if 'factors' in task and 'estimated_time' in task:
464
+ prediction_ratios.append(task['process_time'] / task['estimated_time'])
465
+
466
+ # 如果有足够数据,使用中位数比例调整当前预测
467
+ if prediction_ratios:
468
+ correction_factor = np.median(prediction_ratios)
469
+ # 应用校正因子,但限制在合理范围内
470
+ correction_factor = max(0.5, min(2.0, correction_factor))
471
+ estimated_wait *= correction_factor
472
+
473
+ # 确保等待时间有意义
474
+ estimated_wait = max(0.1, estimated_wait)
475
+ if waiting_tasks == 0 and active_tasks == 0:
476
+ estimated_wait = 0
477
+
478
+ # 获取最近处理的任务
479
+ recent_tasks = task_history[-5:] if task_history else []
480
 
481
  return {
482
  'queue_size': queue_size,
 
484
  'waiting_tasks': waiting_tasks,
485
  'worker_threads': worker_threads,
486
  'estimated_wait': estimated_wait,
 
487
  'recent_tasks': recent_tasks
488
  }
489
 
 
507
  minutes = int((seconds % 3600) / 60)
508
  return f"{hours}小时{minutes}分钟"
509
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
510
  def ui_get_queue_info():
511
  """获取队列信息的UI函数
512
 
 
533
  </tr>
534
  """
535
 
 
 
 
536
  return f"""
537
  <div class="dashboard">
538
  <div class="queue-info-card main-card">
 
553
  </div>
554
 
555
  <div class="wait-time">
 
556
  <p><b>当前预计等待时间:</b> {format_time(queue_info['estimated_wait'])}</p>
557
  <p class="last-update"><small>最后更新: {datetime.now().strftime('%H:%M:%S')}</small></p>
558
  </div>
 
576
  </div>
577
  """
578
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
579
  # 自定义CSS
580
  custom_css = """
581
  .container {