Spaces:
Sleeping
Sleeping
朱东升
commited on
Commit
·
a3558a8
1
Parent(s):
33da27c
update11
Browse files
app.py
CHANGED
@@ -3,13 +3,14 @@ import json
|
|
3 |
import importlib
|
4 |
import os
|
5 |
import sys
|
6 |
-
import time
|
7 |
from pathlib import Path
|
8 |
import concurrent.futures
|
9 |
import multiprocessing
|
|
|
10 |
import threading
|
11 |
import queue
|
12 |
-
|
|
|
13 |
from src.containerized_eval import eval_string_script
|
14 |
|
15 |
# 添加当前目录和src目录到模块搜索路径
|
@@ -20,166 +21,66 @@ if current_dir not in sys.path:
|
|
20 |
if src_dir not in sys.path:
|
21 |
sys.path.append(src_dir)
|
22 |
|
23 |
-
#
|
24 |
task_queue = queue.Queue()
|
25 |
-
|
26 |
-
|
27 |
-
|
28 |
-
|
29 |
-
|
30 |
-
|
31 |
-
#
|
32 |
-
|
33 |
-
|
34 |
-
|
35 |
-
def trigger_ui_update():
|
36 |
-
"""触发UI更新事件"""
|
37 |
-
global last_update_time, status_poll_counter, last_background_update
|
38 |
-
with task_lock:
|
39 |
-
last_update_time = datetime.now() # 更新时间戳
|
40 |
-
status_poll_counter += 1 # 递增计数器,确保每次更新都被捕获
|
41 |
-
last_background_update = last_update_time # 更新后台状态
|
42 |
-
print(f"UI更新被触发: {datetime.now().strftime('%H:%M:%S')} [计数: {status_poll_counter}]")
|
43 |
-
|
44 |
-
def get_next_task_id():
|
45 |
-
global task_id_counter
|
46 |
-
with task_lock:
|
47 |
-
task_id_counter += 1
|
48 |
-
return f"task_{task_id_counter}"
|
49 |
-
|
50 |
-
def submit_task(input_data):
|
51 |
-
"""提交任务到队列
|
52 |
-
|
53 |
-
Args:
|
54 |
-
input_data: 列表(批量处理多个测试用例)
|
55 |
-
|
56 |
-
Returns:
|
57 |
-
str: 任务ID
|
58 |
-
"""
|
59 |
-
try:
|
60 |
-
if not isinstance(input_data, list):
|
61 |
-
return {"status": "error", "message": "Input must be a list"}
|
62 |
-
|
63 |
-
task_id = get_next_task_id()
|
64 |
-
with task_lock:
|
65 |
-
estimated_time = estimate_completion_time(input_data)
|
66 |
-
task_info = {
|
67 |
-
"id": task_id,
|
68 |
-
"data": input_data,
|
69 |
-
"status": "queued",
|
70 |
-
"submitted_at": datetime.now(),
|
71 |
-
"estimated_completion_time": estimated_time,
|
72 |
-
"items_count": len(input_data)
|
73 |
-
}
|
74 |
-
active_tasks[task_id] = task_info
|
75 |
-
task_queue.put(task_info)
|
76 |
-
|
77 |
-
# 触发UI更新
|
78 |
-
trigger_ui_update()
|
79 |
-
|
80 |
-
# 如果这是第一个任务,启动处理线程
|
81 |
-
if len(active_tasks) == 1:
|
82 |
-
threading.Thread(target=process_task_queue, daemon=True).start()
|
83 |
-
|
84 |
-
return {"status": "success", "task_id": task_id}
|
85 |
-
|
86 |
-
except Exception as e:
|
87 |
-
return {"status": "error", "message": str(e)}
|
88 |
|
89 |
-
def
|
90 |
-
"""
|
91 |
-
|
92 |
-
Args:
|
93 |
-
input_data: 任务数据
|
94 |
-
|
95 |
-
Returns:
|
96 |
-
timedelta: 估计的完成时间
|
97 |
-
"""
|
98 |
-
# 在Hugging Face Spaces环境中,资源通常受限,调整处理时间预估
|
99 |
-
# 假设每个任务项平均需要5秒处理(HF环境中可能更慢)
|
100 |
-
avg_time_per_item = 5
|
101 |
-
total_items = len(input_data)
|
102 |
-
|
103 |
-
# Hugging Face Spaces通常有限制的CPU资源
|
104 |
-
# 保守估计并行处理能力
|
105 |
-
try:
|
106 |
-
cpu_count = multiprocessing.cpu_count()
|
107 |
-
except:
|
108 |
-
# 如果获取失败,假设只有2个CPU
|
109 |
-
cpu_count = 2
|
110 |
-
|
111 |
-
# 在HF环境中,即使有多核也可能性能受限,降低并行因子
|
112 |
-
parallel_factor = min(2, total_items) # 限制最多2个并行任务
|
113 |
-
|
114 |
-
if parallel_factor > 0:
|
115 |
-
estimated_seconds = (total_items * avg_time_per_item) / parallel_factor
|
116 |
-
# 为了避免过于乐观的估计,增加30%的缓冲时间
|
117 |
-
estimated_seconds = estimated_seconds * 1.3
|
118 |
-
return timedelta(seconds=round(estimated_seconds))
|
119 |
-
else:
|
120 |
-
return timedelta(seconds=0)
|
121 |
-
|
122 |
-
def process_task_queue():
|
123 |
-
"""处理任务队列的后台线程"""
|
124 |
-
while True:
|
125 |
try:
|
126 |
-
|
127 |
-
|
128 |
-
|
129 |
-
|
130 |
-
|
131 |
-
task_id = task_info["id"]
|
132 |
-
|
133 |
-
# 更新任务状态
|
134 |
-
with task_lock:
|
135 |
-
if task_id in active_tasks: # 确保任务仍存在
|
136 |
-
active_tasks[task_id]["status"] = "processing"
|
137 |
-
print(f"任务 {task_id} 开始处理,当前时间: {datetime.now().strftime('%H:%M:%S')}")
|
138 |
-
trigger_ui_update() # 状态变更为处理中时更新UI
|
139 |
-
else:
|
140 |
-
print(f"警告: 任务 {task_id} 不在活跃任务列表中")
|
141 |
-
task_queue.task_done()
|
142 |
-
continue
|
143 |
|
144 |
# 处理任务
|
145 |
-
|
146 |
-
result = evaluate(task_info["data"])
|
147 |
-
print(f"任务 {task_id} 评估完成,结果数: {len(result) if isinstance(result, list) else 'Not a list'}")
|
148 |
|
149 |
-
#
|
150 |
-
|
151 |
-
|
152 |
-
completed_time = datetime.now()
|
153 |
-
active_tasks[task_id]["status"] = "completed"
|
154 |
-
active_tasks[task_id]["completed_at"] = completed_time
|
155 |
-
active_tasks[task_id]["result"] = result
|
156 |
-
|
157 |
-
# 计算处理持续时间
|
158 |
-
start_time = active_tasks[task_id]["submitted_at"]
|
159 |
-
duration = completed_time - start_time
|
160 |
-
print(f"任务 {task_id} 已完成,耗时: {duration},当前时间: {completed_time.strftime('%H:%M:%S')}")
|
161 |
-
|
162 |
-
# 将任务移至已完成列表
|
163 |
-
completed_tasks.append(active_tasks[task_id])
|
164 |
-
del active_tasks[task_id]
|
165 |
-
|
166 |
-
# 保留最近的20个已完成任务
|
167 |
-
if len(completed_tasks) > 20:
|
168 |
-
completed_tasks.pop(0)
|
169 |
-
|
170 |
-
# 状态更新后强制触发UI更新
|
171 |
-
trigger_ui_update()
|
172 |
-
print(f"任务 {task_id} 完成后触发UI更新: {last_update_time}")
|
173 |
-
else:
|
174 |
-
print(f"警告: 任务 {task_id} 在处理完成后不在活跃任务列表中")
|
175 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
176 |
task_queue.task_done()
|
177 |
|
|
|
|
|
|
|
178 |
except Exception as e:
|
179 |
-
|
180 |
-
|
181 |
-
|
182 |
-
|
|
|
|
|
183 |
|
184 |
def evaluate(input_data):
|
185 |
"""评估代码的主函数
|
@@ -190,67 +91,27 @@ def evaluate(input_data):
|
|
190 |
Returns:
|
191 |
list: 包含评估结果的列表
|
192 |
"""
|
193 |
-
# 打印Gradio版本,用于调试
|
194 |
-
import gradio
|
195 |
-
print(f"Gradio version: {gradio.__version__}")
|
196 |
-
|
197 |
try:
|
198 |
if not isinstance(input_data, list):
|
199 |
return {"status": "Exception", "error": "Input must be a list"}
|
200 |
|
201 |
results = []
|
202 |
-
|
203 |
-
# 在HF Spaces环境中可能受限,降低并行数量
|
204 |
-
try:
|
205 |
-
max_workers = min(multiprocessing.cpu_count(), 2) # 最多2个并行任务
|
206 |
-
except:
|
207 |
-
max_workers = 2 # 如果无法获取,默认为2
|
208 |
-
|
209 |
-
# 增加超时处理
|
210 |
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
211 |
-
future_to_item = {}
|
212 |
-
|
213 |
-
|
214 |
-
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
# 等待当前批次完成
|
222 |
-
for future in concurrent.futures.as_completed(future_to_item):
|
223 |
-
item = future_to_item[future]
|
224 |
-
try:
|
225 |
-
# 设置较短的超时时间,避免任务卡死
|
226 |
-
result = future.result(timeout=60) # 60秒超时
|
227 |
-
item.update(result)
|
228 |
-
results.append(item)
|
229 |
-
except concurrent.futures.TimeoutError:
|
230 |
-
# 处理超时情况
|
231 |
-
item.update({
|
232 |
-
"status": "Timeout",
|
233 |
-
"error": "Task processing timed out in Hugging Face environment"
|
234 |
-
})
|
235 |
-
results.append(item)
|
236 |
-
except Exception as e:
|
237 |
-
# 处理其他异常
|
238 |
-
item.update({
|
239 |
-
"status": "Exception",
|
240 |
-
"error": f"Error in Hugging Face environment: {str(e)}"
|
241 |
-
})
|
242 |
-
results.append(item)
|
243 |
-
|
244 |
-
# 清空当前批次
|
245 |
-
future_to_item = {}
|
246 |
-
|
247 |
-
# 短暂休息,让系统喘息
|
248 |
-
time.sleep(0.5)
|
249 |
-
|
250 |
return results
|
251 |
|
252 |
except Exception as e:
|
253 |
-
return {"status": "Exception", "error":
|
254 |
|
255 |
def evaluate_single_case(input_data):
|
256 |
"""评估单个代码用例
|
@@ -280,7 +141,7 @@ def evaluate_single_case(input_data):
|
|
280 |
results.append(result)
|
281 |
|
282 |
return results[0]
|
283 |
-
|
284 |
except Exception as e:
|
285 |
return {"status": "Exception", "error": str(e)}
|
286 |
|
@@ -302,601 +163,472 @@ def evaluate_code(code, language):
|
|
302 |
except Exception as e:
|
303 |
return {"status": "Exception", "error": str(e)}
|
304 |
|
305 |
-
def
|
306 |
-
"""
|
|
|
|
|
307 |
|
|
|
|
|
|
|
308 |
Returns:
|
309 |
-
dict:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
310 |
"""
|
311 |
-
|
312 |
-
|
313 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
314 |
|
315 |
-
|
316 |
-
|
317 |
-
|
318 |
-
|
319 |
-
|
|
|
320 |
|
321 |
-
|
322 |
-
total_items = sum(task["items_count"] for task in active_tasks.values())
|
323 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
324 |
return {
|
325 |
-
|
326 |
-
|
327 |
-
|
328 |
-
|
329 |
-
|
330 |
-
|
331 |
-
{
|
332 |
-
"id": task["id"],
|
333 |
-
"status": task["status"],
|
334 |
-
"items_count": task["items_count"],
|
335 |
-
"submitted_at": task["submitted_at"].strftime("%Y-%m-%d %H:%M:%S"),
|
336 |
-
"estimated_completion": str(task["estimated_completion_time"])
|
337 |
-
} for task in active_tasks.values()
|
338 |
-
],
|
339 |
-
"recent_completed": [
|
340 |
-
{
|
341 |
-
"id": task["id"],
|
342 |
-
"items_count": task["items_count"],
|
343 |
-
"submitted_at": task["submitted_at"].strftime("%Y-%m-%d %H:%M:%S"),
|
344 |
-
"completed_at": task["completed_at"].strftime("%Y-%m-%d %H:%M:%S") if "completed_at" in task else "",
|
345 |
-
"duration": str(task["completed_at"] - task["submitted_at"]) if "completed_at" in task else ""
|
346 |
-
} for task in completed_tasks[-5:] # 只显示最近5个完成的任务
|
347 |
-
],
|
348 |
-
"poll_counter": status_poll_counter, # 添加轮询计数器
|
349 |
-
"timestamp": datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
|
350 |
}
|
351 |
|
352 |
-
def
|
353 |
-
"""
|
354 |
|
|
|
|
|
|
|
355 |
Returns:
|
356 |
-
str:
|
357 |
"""
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
|
367 |
-
|
368 |
-
|
369 |
-
|
370 |
-
|
371 |
-
|
372 |
-
|
373 |
-
|
374 |
-
</div>
|
375 |
-
</div>
|
376 |
|
377 |
-
|
378 |
-
|
379 |
-
|
380 |
-
|
381 |
-
|
382 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
383 |
</div>
|
|
|
384 |
|
385 |
-
|
386 |
-
|
387 |
-
|
388 |
-
|
389 |
-
|
390 |
-
|
391 |
-
|
392 |
-
|
393 |
-
|
394 |
-
|
395 |
-
|
396 |
-
|
397 |
-
<tbody>
|
398 |
"""
|
|
|
|
|
399 |
|
400 |
-
|
401 |
-
|
402 |
-
|
403 |
-
|
404 |
-
|
405 |
-
|
406 |
-
|
407 |
-
|
408 |
-
|
409 |
-
|
410 |
-
|
411 |
-
|
412 |
-
|
413 |
-
|
414 |
-
|
415 |
-
|
416 |
-
|
417 |
-
|
418 |
-
<tr>
|
419 |
-
<td colspan="5" style="padding: 15px; text-align: center; color: #777;">当前没有活跃任务</td>
|
420 |
-
</tr>
|
421 |
"""
|
422 |
-
|
423 |
-
|
424 |
-
|
425 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
426 |
</div>
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
441 |
"""
|
|
|
442 |
|
443 |
-
|
444 |
-
|
445 |
-
|
446 |
-
|
447 |
-
|
448 |
-
|
449 |
-
|
450 |
-
|
451 |
-
<td style="padding: 12px; border-bottom: 1px solid #eee;">{task['duration']}</td>
|
452 |
-
</tr>
|
453 |
-
"""
|
454 |
-
else:
|
455 |
-
html += f"""
|
456 |
-
<tr>
|
457 |
-
<td colspan="5" style="padding: 15px; text-align: center; color: #777;">暂无已完成任务</td>
|
458 |
-
</tr>
|
459 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
460 |
|
461 |
-
|
462 |
-
|
463 |
-
</table>
|
464 |
</div>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
465 |
</div>
|
466 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
467 |
|
468 |
-
|
|
|
|
|
|
|
469 |
|
470 |
-
def
|
471 |
-
"""
|
472 |
-
global
|
473 |
-
|
474 |
-
|
|
|
|
|
|
|
|
|
|
|
475 |
|
476 |
-
|
477 |
-
|
478 |
-
|
479 |
-
|
480 |
-
|
481 |
-
|
482 |
-
"poll_counter": status_poll_counter,
|
483 |
-
"timestamp": datetime.now().strftime('%Y-%m-%d %H:%M:%S.%f')
|
484 |
-
}
|
485 |
|
486 |
-
|
487 |
-
|
488 |
-
|
489 |
-
|
490 |
-
|
491 |
-
|
492 |
-
|
493 |
-
|
494 |
-
|
495 |
-
|
496 |
-
|
497 |
-
|
498 |
-
|
499 |
-
|
500 |
-
|
501 |
-
|
502 |
-
|
503 |
-
|
504 |
-
|
505 |
-
|
506 |
-
|
507 |
-
|
508 |
-
|
509 |
-
|
510 |
-
|
511 |
-
|
512 |
-
|
513 |
-
|
514 |
-
#
|
515 |
-
|
516 |
-
|
517 |
-
|
518 |
-
|
519 |
-
|
520 |
-
|
521 |
-
|
522 |
-
|
523 |
-
|
524 |
-
|
525 |
-
|
526 |
-
|
527 |
-
|
528 |
-
|
529 |
-
|
530 |
-
|
531 |
-
|
532 |
-
|
533 |
-
|
534 |
-
|
535 |
-
|
536 |
-
|
537 |
-
|
538 |
-
|
539 |
-
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
548 |
-
|
549 |
-
|
550 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
551 |
|
552 |
# 创建Gradio接口
|
553 |
-
with gr.Blocks(
|
554 |
-
gr.Markdown(""
|
555 |
-
|
556 |
-
### 支持多种编程语言的代码评估服务
|
557 |
-
""")
|
558 |
-
|
559 |
-
# 添加隐藏的API处理组件
|
560 |
-
with gr.Row(visible=False):
|
561 |
-
api_input = gr.JSON()
|
562 |
-
api_output = gr.JSON()
|
563 |
-
|
564 |
-
# 设置API触发器
|
565 |
-
def api_trigger(data):
|
566 |
-
"""处理API请求的函数"""
|
567 |
-
print(f"通过API接收到请求: {len(data) if isinstance(data, list) else 'Not a list'}")
|
568 |
-
try:
|
569 |
-
result = submit_task(data)
|
570 |
-
# 强制触发UI更新
|
571 |
-
trigger_ui_update()
|
572 |
-
return result
|
573 |
-
except Exception as e:
|
574 |
-
print(f"API处理出错: {str(e)}")
|
575 |
-
return {"status": "error", "message": str(e)}
|
576 |
-
|
577 |
-
api_input.change(fn=api_trigger, inputs=api_input, outputs=api_output)
|
578 |
|
579 |
-
with gr.Tab("
|
580 |
-
|
581 |
-
|
582 |
-
|
583 |
-
|
584 |
-
|
585 |
-
|
586 |
-
if 'gradio_version' not in locals():
|
587 |
-
import gradio
|
588 |
-
gradio_version = getattr(gradio, "__version__", "unknown")
|
589 |
-
|
590 |
-
if gradio_version.startswith("3."):
|
591 |
-
# Gradio 3.x 方式
|
592 |
-
refresh_button.click(fn=refresh_ui, outputs=[status_html, poll_counter], concurrency_limit=2)
|
593 |
-
else:
|
594 |
-
# Gradio 4.x 方式 (不使用concurrency_limit参数)
|
595 |
-
refresh_button.click(
|
596 |
-
fn=refresh_ui,
|
597 |
-
outputs=[status_html, poll_counter],
|
598 |
-
every=10 # 每10秒自动刷新一次
|
599 |
-
)
|
600 |
-
|
601 |
-
# API断点,用于轮询最新状态
|
602 |
-
status_polling_input = gr.Number(value=0, visible=False, label="轮询触发器")
|
603 |
-
status_polling_output = gr.JSON(visible=False, label="轮询结果")
|
604 |
-
|
605 |
-
status_polling_input.change(
|
606 |
-
fn=get_status_for_polling,
|
607 |
-
inputs=[],
|
608 |
-
outputs=status_polling_output
|
609 |
-
)
|
610 |
-
|
611 |
-
# 使用JavaScript实现自动轮询
|
612 |
-
polling_js = """
|
613 |
-
<script>
|
614 |
-
// 初始化轮询计数器
|
615 |
-
let lastPollCounter = 0;
|
616 |
-
let pollingActive = true;
|
617 |
-
|
618 |
-
// 轮询函数
|
619 |
-
async function pollStatus() {
|
620 |
-
if (!pollingActive) return;
|
621 |
-
|
622 |
-
try {
|
623 |
-
// 查找JSON输出元素 (在Gradio 4.x中,元素ID可能不同)
|
624 |
-
const jsonElements = document.querySelectorAll('[data-testid="json"]');
|
625 |
-
const statusElements = document.querySelectorAll('[id*="status_html"]');
|
626 |
-
|
627 |
-
// 如果找不到元素,使用普通AJAX请求
|
628 |
-
if (!jsonElements.length) {
|
629 |
-
// 通过已有的刷新按钮点击来刷新
|
630 |
-
const refreshButton = [...document.querySelectorAll('button')].find(btn =>
|
631 |
-
btn.textContent.includes('刷新状态'));
|
632 |
-
|
633 |
-
if (refreshButton) {
|
634 |
-
refreshButton.click();
|
635 |
-
console.log("刷新按钮被触发");
|
636 |
-
}
|
637 |
-
} else {
|
638 |
-
// 使用Gradio API调用来获取最新状态
|
639 |
-
// 构建API请求的路径
|
640 |
-
const apiBase = window.location.pathname.endsWith('/')
|
641 |
-
? window.location.pathname
|
642 |
-
: window.location.pathname + '/';
|
643 |
-
|
644 |
-
const response = await fetch(apiBase + 'api/queue/status');
|
645 |
-
if (response.ok) {
|
646 |
-
const data = await response.json();
|
647 |
-
const statusHtml = statusElements[0];
|
648 |
-
|
649 |
-
// 如果有状态更新,更新UI
|
650 |
-
if (statusHtml && data.status) {
|
651 |
-
// 根据API返回更新页面状态
|
652 |
-
console.log("服务器状态已更新");
|
653 |
-
|
654 |
-
// 触发刷新按钮
|
655 |
-
const refreshButton = [...document.querySelectorAll('button')].find(btn =>
|
656 |
-
btn.textContent.includes('刷新状态'));
|
657 |
-
|
658 |
-
if (refreshButton) {
|
659 |
-
refreshButton.click();
|
660 |
-
}
|
661 |
-
}
|
662 |
-
}
|
663 |
-
}
|
664 |
-
} catch (err) {
|
665 |
-
console.error("轮询错误:", err);
|
666 |
-
}
|
667 |
-
|
668 |
-
// 继续轮询
|
669 |
-
setTimeout(pollStatus, 5000); // 每5秒轮询一次
|
670 |
-
}
|
671 |
-
|
672 |
-
// 页面加载完成后开始轮询
|
673 |
-
if (document.readyState === 'loading') {
|
674 |
-
document.addEventListener('DOMContentLoaded', () => setTimeout(pollStatus, 1000));
|
675 |
-
} else {
|
676 |
-
setTimeout(pollStatus, 1000);
|
677 |
-
}
|
678 |
|
679 |
-
|
680 |
-
|
681 |
-
|
682 |
-
if (pollingActive) {
|
683 |
-
// 页面变为可见时立即轮询一次
|
684 |
-
pollStatus();
|
685 |
-
}
|
686 |
-
});
|
687 |
-
</script>
|
688 |
-
"""
|
689 |
-
|
690 |
-
gr.HTML(polling_js)
|
691 |
-
|
692 |
-
# 以下是原来的自动刷新脚本,保留但不使用
|
693 |
-
auto_refresh_js = """
|
694 |
-
<script>
|
695 |
-
// 兼容Gradio 3.x和4.x的自动刷新机制 - 仅作为备用
|
696 |
-
console.log('自动刷新机制已加载,但已被新的轮询系统替代');
|
697 |
-
</script>
|
698 |
-
"""
|
699 |
-
|
700 |
-
# 不显示旧的自动刷新脚本
|
701 |
-
# gr.HTML(auto_refresh_js)
|
702 |
|
703 |
-
with gr.Tab("
|
704 |
with gr.Row():
|
705 |
-
with gr.Column():
|
706 |
-
|
707 |
-
|
708 |
-
|
709 |
-
lines=10
|
710 |
-
)
|
711 |
-
submit_button = gr.Button("提交任务")
|
712 |
|
713 |
-
with gr.Column():
|
714 |
-
|
715 |
-
|
716 |
-
|
717 |
-
|
718 |
-
|
719 |
-
|
720 |
-
|
721 |
-
|
722 |
-
|
723 |
-
|
724 |
-
with gr.Tab("API文档"):
|
725 |
-
gr.Markdown("""
|
726 |
-
## API 文档
|
727 |
-
|
728 |
-
### 1. 提交任务
|
729 |
-
|
730 |
-
**请求:**
|
731 |
-
|
732 |
-
```
|
733 |
-
POST /api/predict
|
734 |
-
Content-Type: application/json
|
735 |
-
|
736 |
-
[
|
737 |
-
{
|
738 |
-
"language": "python",
|
739 |
-
"prompt": "def add(a, b):\\n",
|
740 |
-
"processed_completions": [" return a + b"],
|
741 |
-
"tests": "assert add(1, 2) == 3"
|
742 |
-
}
|
743 |
-
]
|
744 |
-
```
|
745 |
-
|
746 |
-
**响应:**
|
747 |
-
|
748 |
-
```json
|
749 |
-
{
|
750 |
-
"status": "success",
|
751 |
-
"task_id": "task_1"
|
752 |
-
}
|
753 |
-
```
|
754 |
-
|
755 |
-
### 2. 查询任务状态
|
756 |
-
|
757 |
-
**请求:**
|
758 |
-
|
759 |
-
```
|
760 |
-
GET /api/status
|
761 |
-
```
|
762 |
-
|
763 |
-
**响应:**
|
764 |
-
|
765 |
-
```json
|
766 |
-
{
|
767 |
-
"queued_tasks": 1,
|
768 |
-
"processing_tasks": 2,
|
769 |
-
"total_tasks": 3,
|
770 |
-
"total_items": 15,
|
771 |
-
"estimated_completion_time": "0:05:30",
|
772 |
-
"active_tasks": [...],
|
773 |
-
"recent_completed": [...]
|
774 |
-
}
|
775 |
-
```
|
776 |
-
""")
|
777 |
-
|
778 |
-
# 这里不再添加状态API端点,避免与FastAPI冲突
|
779 |
-
# demo.queue(api_open=True).add_api_route("/api/queue/status", api_get_queue_status, methods=["GET"])
|
780 |
-
|
781 |
-
if __name__ == "__main__":
|
782 |
-
# 检测Gradio版本以适配不同版本的API
|
783 |
-
import gradio
|
784 |
-
gradio_version = getattr(gradio, "__version__", "unknown")
|
785 |
-
print(f"当前Gradio版本: {gradio_version}")
|
786 |
|
787 |
-
#
|
788 |
-
|
789 |
-
|
790 |
-
|
|
|
791 |
|
|
|
|
|
|
|
|
|
|
|
792 |
try:
|
793 |
-
|
794 |
-
|
795 |
-
|
796 |
-
|
797 |
-
logger = logging.getLogger("CodeEvalService")
|
798 |
-
logger.info(f"启动代码评估服务,Gradio版本: {gradio_version}")
|
799 |
-
|
800 |
-
# 尝试使用兼容所有版本的参数启动
|
801 |
-
launch_kwargs = {
|
802 |
-
"server_name": "0.0.0.0",
|
803 |
-
"server_port": int(os.environ.get("PORT", 7860)),
|
804 |
-
"share": False,
|
805 |
-
}
|
806 |
-
|
807 |
-
# Gradio 4.x专用的FastAPI初始化
|
808 |
-
if not gradio_version.startswith("3."):
|
809 |
-
# 针对Gradio 4的API配置
|
810 |
-
import fastapi
|
811 |
-
from fastapi import FastAPI
|
812 |
-
|
813 |
-
# 创建FastAPI应用
|
814 |
-
app = FastAPI(title="代码评估服务API")
|
815 |
-
|
816 |
-
# 添加队列状态API端点
|
817 |
-
@app.get("/api/queue/status")
|
818 |
-
def get_queue_status_api():
|
819 |
-
return api_get_queue_status()
|
820 |
-
|
821 |
-
# 添加任务提交API端点
|
822 |
-
@app.post("/api/submit_task")
|
823 |
-
async def submit_task_api(data: list):
|
824 |
-
try:
|
825 |
-
result = submit_task(data)
|
826 |
-
return result
|
827 |
-
except Exception as e:
|
828 |
-
return {"status": "error", "message": str(e)}
|
829 |
-
|
830 |
-
# 添加/evaluate API端点
|
831 |
-
@app.post("/evaluate")
|
832 |
-
async def evaluate_api(data: list):
|
833 |
-
try:
|
834 |
-
result = evaluate(data)
|
835 |
-
return result
|
836 |
-
except Exception as e:
|
837 |
-
return {"status": "error", "message": str(e)}
|
838 |
-
|
839 |
-
# 启动应用到FastAPI
|
840 |
-
demo.launch(
|
841 |
-
**launch_kwargs,
|
842 |
-
app=app,
|
843 |
-
max_threads=5,
|
844 |
-
)
|
845 |
-
else:
|
846 |
-
# Gradio 3.x的方式
|
847 |
-
# 设置队列,并添加API支持
|
848 |
-
try:
|
849 |
-
demo.queue(api_open=True, max_size=30)
|
850 |
-
# 添加/evaluate API端点
|
851 |
-
demo.add_api_route("/evaluate", evaluate, methods=["POST"])
|
852 |
-
except Exception as e:
|
853 |
-
logger.warning(f"配置队列时出错: {e}")
|
854 |
-
|
855 |
-
# 启动应用
|
856 |
-
demo.launch(
|
857 |
-
**launch_kwargs,
|
858 |
-
debug=False,
|
859 |
-
show_api=True,
|
860 |
-
max_threads=5,
|
861 |
-
concurrency_limit=2
|
862 |
-
)
|
863 |
-
|
864 |
-
except Exception as e:
|
865 |
-
print(f"启动时��生错误: {e}")
|
866 |
-
import traceback
|
867 |
-
traceback.print_exc()
|
868 |
-
|
869 |
-
# 尝试最简配置启动
|
870 |
-
try:
|
871 |
-
print("使用最小配置重试...")
|
872 |
-
demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))
|
873 |
-
except Exception as e2:
|
874 |
-
print(f"最小配置启动也失败: {e2}")
|
875 |
-
traceback.print_exc()
|
876 |
-
|
877 |
-
# 终极回退方案:创建最简单的接口并启动
|
878 |
-
try:
|
879 |
-
print("尝试创建备用界面...")
|
880 |
-
|
881 |
-
import gradio as gr
|
882 |
-
def simple_evaluate(json_data):
|
883 |
-
try:
|
884 |
-
print(f"备用界面收到请求: {json_data[:100] if isinstance(json_data, str) else 'Not a string'}")
|
885 |
-
data = json.loads(json_data) if isinstance(json_data, str) else json_data
|
886 |
-
result = submit_task(data)
|
887 |
-
return json.dumps(result, ensure_ascii=False)
|
888 |
-
except Exception as e:
|
889 |
-
return {"error": str(e)}
|
890 |
-
|
891 |
-
backup_demo = gr.Interface(
|
892 |
-
fn=simple_evaluate,
|
893 |
-
inputs=gr.Textbox(label="JSON输入"),
|
894 |
-
outputs=gr.Textbox(label="结果"),
|
895 |
-
title="代码评估服务 (备用界面)",
|
896 |
-
description="原界面启动失败,这是简化版本。提交格式: [{\"language\": \"python\", \"prompt\": \"def add(a, b):\\n\", \"processed_completions\": [\" return a + b\"], \"tests\": \"assert add(1, 2) == 3\"}]"
|
897 |
-
)
|
898 |
-
|
899 |
-
backup_demo.launch(server_name="0.0.0.0", server_port=int(os.environ.get("PORT", 7860)))
|
900 |
-
except Exception as e3:
|
901 |
-
print(f"备用界面也启动失败: {e3}")
|
902 |
-
traceback.print_exc()
|
|
|
3 |
import importlib
|
4 |
import os
|
5 |
import sys
|
|
|
6 |
from pathlib import Path
|
7 |
import concurrent.futures
|
8 |
import multiprocessing
|
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目录到模块搜索路径
|
|
|
21 |
if src_dir not in sys.path:
|
22 |
sys.path.append(src_dir)
|
23 |
|
24 |
+
# 创建消息队列
|
25 |
task_queue = queue.Queue()
|
26 |
+
# 存储任务状态的字典
|
27 |
+
task_status = {}
|
28 |
+
# 存储任务历史的列表,最多保存最近10个任务
|
29 |
+
task_history = []
|
30 |
+
# 用于保护共享资源的锁
|
31 |
+
lock = threading.Lock()
|
32 |
+
# 工作线程数
|
33 |
+
worker_threads = multiprocessing.cpu_count()
|
34 |
+
# 后台线程是否运行的标志
|
35 |
+
running = True
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
36 |
|
37 |
+
def queue_processor():
|
38 |
+
"""处理队列中的任务"""
|
39 |
+
while running:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
40 |
try:
|
41 |
+
# 从队列中获取任务,如果队列为空等待0.1秒
|
42 |
+
task_id, input_data, request_time = task_queue.get(timeout=0.1)
|
43 |
+
with lock:
|
44 |
+
task_status[task_id]['status'] = 'processing'
|
45 |
+
task_status[task_id]['start_time'] = time.time()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
46 |
|
47 |
# 处理任务
|
48 |
+
result = evaluate(input_data)
|
|
|
|
|
49 |
|
50 |
+
# 更新任务状态
|
51 |
+
end_time = time.time()
|
52 |
+
process_time = end_time - task_status[task_id]['start_time']
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
53 |
|
54 |
+
with lock:
|
55 |
+
task_status[task_id]['status'] = 'completed'
|
56 |
+
task_status[task_id]['result'] = result
|
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 |
+
# 标记任务完成
|
72 |
task_queue.task_done()
|
73 |
|
74 |
+
except queue.Empty:
|
75 |
+
# 队列为空,继续等待
|
76 |
+
continue
|
77 |
except Exception as e:
|
78 |
+
if 'task_id' in locals():
|
79 |
+
with lock:
|
80 |
+
task_status[task_id]['status'] = 'error'
|
81 |
+
task_status[task_id]['error'] = str(e)
|
82 |
+
task_status[task_id]['end_time'] = time.time()
|
83 |
+
task_queue.task_done()
|
84 |
|
85 |
def evaluate(input_data):
|
86 |
"""评估代码的主函数
|
|
|
91 |
Returns:
|
92 |
list: 包含评估结果的列表
|
93 |
"""
|
|
|
|
|
|
|
|
|
94 |
try:
|
95 |
if not isinstance(input_data, list):
|
96 |
return {"status": "Exception", "error": "Input must be a list"}
|
97 |
|
98 |
results = []
|
99 |
+
max_workers = multiprocessing.cpu_count()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
100 |
with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
|
101 |
+
future_to_item = {executor.submit(evaluate_single_case, item): item for item in input_data}
|
102 |
+
for future in concurrent.futures.as_completed(future_to_item):
|
103 |
+
item = future_to_item[future]
|
104 |
+
try:
|
105 |
+
result = future.result()
|
106 |
+
item.update(result)
|
107 |
+
results.append(item)
|
108 |
+
except Exception as e:
|
109 |
+
item.update({"status": "Exception", "error": str(e)})
|
110 |
+
results.append(item)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
111 |
return results
|
112 |
|
113 |
except Exception as e:
|
114 |
+
return {"status": "Exception", "error": str(e)}
|
115 |
|
116 |
def evaluate_single_case(input_data):
|
117 |
"""评估单个代码用例
|
|
|
141 |
results.append(result)
|
142 |
|
143 |
return results[0]
|
144 |
+
|
145 |
except Exception as e:
|
146 |
return {"status": "Exception", "error": str(e)}
|
147 |
|
|
|
163 |
except Exception as e:
|
164 |
return {"status": "Exception", "error": str(e)}
|
165 |
|
166 |
+
def synchronous_evaluate(input_data):
|
167 |
+
"""同步评估代码,兼容原来的接口
|
168 |
+
|
169 |
+
这个函数会阻塞直到评估完成,然后返回结果
|
170 |
|
171 |
+
Args:
|
172 |
+
input_data: 要评估的输入数据
|
173 |
+
|
174 |
Returns:
|
175 |
+
dict: 评估结果
|
176 |
+
"""
|
177 |
+
# 获取队列当前状态
|
178 |
+
queue_info = get_queue_status()
|
179 |
+
waiting_tasks = queue_info['waiting_tasks']
|
180 |
+
|
181 |
+
# 创建任务
|
182 |
+
task_id = str(uuid.uuid4())
|
183 |
+
request_time = time.time()
|
184 |
+
|
185 |
+
with lock:
|
186 |
+
# 创建任务状态记录
|
187 |
+
task_status[task_id] = {
|
188 |
+
'status': 'queued',
|
189 |
+
'queued_time': request_time,
|
190 |
+
'queue_position': task_queue.qsize() + 1,
|
191 |
+
'synchronous': True # 标记为同步任务
|
192 |
+
}
|
193 |
+
|
194 |
+
# 将任务添加到队列
|
195 |
+
task_queue.put((task_id, input_data, request_time))
|
196 |
+
|
197 |
+
# 等待任务完成
|
198 |
+
while True:
|
199 |
+
with lock:
|
200 |
+
if task_id in task_status:
|
201 |
+
status = task_status[task_id]['status']
|
202 |
+
if status == 'completed':
|
203 |
+
result = task_status[task_id]['result']
|
204 |
+
# 任务完成后清理状态
|
205 |
+
task_status.pop(task_id, None)
|
206 |
+
return result
|
207 |
+
elif status == 'error':
|
208 |
+
error = task_status[task_id].get('error', '未知错误')
|
209 |
+
# 任务出错后清理状态
|
210 |
+
task_status.pop(task_id, None)
|
211 |
+
return {"status": "Exception", "error": error}
|
212 |
+
|
213 |
+
# 短暂睡眠避免CPU占用过高
|
214 |
+
time.sleep(0.1)
|
215 |
+
|
216 |
+
def enqueue_task(input_data):
|
217 |
+
"""将任务添加到队列
|
218 |
+
|
219 |
+
Args:
|
220 |
+
input_data: 要处理的任务数据
|
221 |
+
|
222 |
+
Returns:
|
223 |
+
dict: 包含任务ID和状态的字典
|
224 |
"""
|
225 |
+
task_id = str(uuid.uuid4())
|
226 |
+
request_time = time.time()
|
227 |
+
|
228 |
+
with lock:
|
229 |
+
# 创建任务状态��录
|
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 |
+
|
239 |
+
return {
|
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):
|
247 |
+
"""检查任务状态
|
248 |
+
|
249 |
+
Args:
|
250 |
+
task_id: 任务ID
|
251 |
|
252 |
+
Returns:
|
253 |
+
dict: 包含任务状态的字典
|
254 |
+
"""
|
255 |
+
with lock:
|
256 |
+
if task_id not in task_status:
|
257 |
+
return {'status': 'not_found'}
|
258 |
|
259 |
+
status_info = task_status[task_id].copy()
|
|
|
260 |
|
261 |
+
# 如果任务已完成,从状态字典中移除(避免内存泄漏)
|
262 |
+
if status_info['status'] in ['completed', 'error'] and time.time() - status_info.get('end_time', 0) > 3600:
|
263 |
+
task_status.pop(task_id, None)
|
264 |
+
|
265 |
+
return status_info
|
266 |
+
|
267 |
+
def get_queue_status():
|
268 |
+
"""获取队列状态
|
269 |
+
|
270 |
+
Returns:
|
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 |
+
avg_time = 0
|
280 |
+
if recent_tasks:
|
281 |
+
avg_time = sum(task['process_time'] for task in recent_tasks) / len(recent_tasks)
|
282 |
+
|
283 |
return {
|
284 |
+
'queue_size': queue_size,
|
285 |
+
'active_tasks': active_tasks,
|
286 |
+
'waiting_tasks': waiting_tasks,
|
287 |
+
'worker_threads': worker_threads,
|
288 |
+
'estimated_wait': waiting_tasks * avg_time if avg_time > 0 else waiting_tasks * 5,
|
289 |
+
'recent_tasks': recent_tasks
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
290 |
}
|
291 |
|
292 |
+
def format_time(seconds):
|
293 |
+
"""格式化时间为易读格式
|
294 |
|
295 |
+
Args:
|
296 |
+
seconds: 秒数
|
297 |
+
|
298 |
Returns:
|
299 |
+
str: 格式化的时间字符串
|
300 |
"""
|
301 |
+
if seconds < 60:
|
302 |
+
return f"{seconds:.1f}秒"
|
303 |
+
elif seconds < 3600:
|
304 |
+
minutes = int(seconds / 60)
|
305 |
+
seconds = seconds % 60
|
306 |
+
return f"{minutes}分{seconds:.1f}秒"
|
307 |
+
else:
|
308 |
+
hours = int(seconds / 3600)
|
309 |
+
minutes = int((seconds % 3600) / 60)
|
310 |
+
return f"{hours}小时{minutes}分钟"
|
311 |
+
|
312 |
+
def ui_submit(input_json):
|
313 |
+
"""提交任务到队列的UI函数
|
314 |
+
|
315 |
+
Args:
|
316 |
+
input_json: JSON格式的输入数据
|
|
|
|
|
317 |
|
318 |
+
Returns:
|
319 |
+
tuple: 任务ID和初始状态信息
|
320 |
+
"""
|
321 |
+
try:
|
322 |
+
input_data = json.loads(input_json) if isinstance(input_json, str) else input_json
|
323 |
+
response = enqueue_task(input_data)
|
324 |
+
task_id = response['task_id']
|
325 |
+
|
326 |
+
status_html = f"""
|
327 |
+
<div class="status-card">
|
328 |
+
<h3>任务状态</h3>
|
329 |
+
<p><b>任务ID:</b> {task_id}</p>
|
330 |
+
<p><b>状态:</b> 已入队</p>
|
331 |
+
<p><b>队列位置:</b> {response['queue_position']}</p>
|
332 |
+
<p><b>预计等待时间:</b> {format_time(response['estimated_wait'])}</p>
|
333 |
</div>
|
334 |
+
"""
|
335 |
|
336 |
+
return task_id, status_html
|
337 |
+
except Exception as e:
|
338 |
+
return None, f"<div class='error-message'>提交失败: {str(e)}</div>"
|
339 |
+
|
340 |
+
def ui_check_status(task_id):
|
341 |
+
"""检查任务状态的UI函数
|
342 |
+
|
343 |
+
Args:
|
344 |
+
task_id: 任务ID
|
345 |
+
|
346 |
+
Returns:
|
347 |
+
str: 包含任务状态的HTML
|
|
|
348 |
"""
|
349 |
+
if not task_id:
|
350 |
+
return "<div class='notice'>请提供任务ID</div>"
|
351 |
|
352 |
+
status = check_status(task_id)
|
353 |
+
|
354 |
+
if status['status'] == 'not_found':
|
355 |
+
return "<div class='error-message'>任务未找到</div>"
|
356 |
+
|
357 |
+
if status['status'] == 'queued':
|
358 |
+
queue_info = get_queue_status()
|
359 |
+
est_wait = queue_info['estimated_wait'] / queue_info['waiting_tasks'] * status['queue_position'] if queue_info['waiting_tasks'] > 0 else 0
|
360 |
+
|
361 |
+
return f"""
|
362 |
+
<div class="status-card">
|
363 |
+
<h3>任务状态</h3>
|
364 |
+
<p><b>任务ID:</b> {task_id}</p>
|
365 |
+
<p><b>状态:</b> 等待中</p>
|
366 |
+
<p><b>队列位置:</b> {status['queue_position']}</p>
|
367 |
+
<p><b>入队时间:</b> {datetime.fromtimestamp(status['queued_time']).strftime('%H:%M:%S')}</p>
|
368 |
+
<p><b>预计等待时间:</b> {format_time(est_wait)}</p>
|
369 |
+
</div>
|
|
|
|
|
|
|
370 |
"""
|
371 |
+
|
372 |
+
if status['status'] == 'processing':
|
373 |
+
process_time = time.time() - status['start_time']
|
374 |
+
|
375 |
+
return f"""
|
376 |
+
<div class="status-card">
|
377 |
+
<h3>任务状态</h3>
|
378 |
+
<p><b>任务ID:</b> {task_id}</p>
|
379 |
+
<p><b>状态:</b> 处理中</p>
|
380 |
+
<p><b>已处理时间:</b> {format_time(process_time)}</p>
|
381 |
+
<p><b>入队时间:</b> {datetime.fromtimestamp(status['queued_time']).strftime('%H:%M:%S')}</p>
|
382 |
</div>
|
383 |
+
"""
|
384 |
+
|
385 |
+
if status['status'] == 'completed':
|
386 |
+
result = status.get('result', {})
|
387 |
+
|
388 |
+
return f"""
|
389 |
+
<div class="status-card success">
|
390 |
+
<h3>任务状态</h3>
|
391 |
+
<p><b>任务ID:</b> {task_id}</p>
|
392 |
+
<p><b>状态:</b> 已完成</p>
|
393 |
+
<p><b>处理时间:</b> {format_time(status['process_time'])}</p>
|
394 |
+
<p><b>完成时间:</b> {datetime.fromtimestamp(status['end_time']).strftime('%H:%M:%S')}</p>
|
395 |
+
</div>
|
396 |
+
"""
|
397 |
+
|
398 |
+
if status['status'] == 'error':
|
399 |
+
return f"""
|
400 |
+
<div class="status-card error">
|
401 |
+
<h3>任务状态</h3>
|
402 |
+
<p><b>任务ID:</b> {task_id}</p>
|
403 |
+
<p><b>状态:</b> 错误</p>
|
404 |
+
<p><b>错误信息:</b> {status.get('error', '未知错误')}</p>
|
405 |
+
</div>
|
406 |
+
"""
|
407 |
+
|
408 |
+
return "<div class='error-message'>未知状态</div>"
|
409 |
+
|
410 |
+
def ui_get_queue_info():
|
411 |
+
"""获取队列信息的UI函数
|
412 |
+
|
413 |
+
Returns:
|
414 |
+
str: 包含队列信息的HTML
|
415 |
"""
|
416 |
+
queue_info = get_queue_status()
|
417 |
|
418 |
+
tasks_html = ""
|
419 |
+
for task in reversed(queue_info['recent_tasks']):
|
420 |
+
tasks_html += f"""
|
421 |
+
<tr>
|
422 |
+
<td>{task['task_id'][:8]}...</td>
|
423 |
+
<td>{datetime.fromtimestamp(task['request_time']).strftime('%H:%M:%S')}</td>
|
424 |
+
<td>{format_time(task['process_time'])}</td>
|
425 |
+
</tr>
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
426 |
"""
|
427 |
+
|
428 |
+
return f"""
|
429 |
+
<div class="queue-info-card">
|
430 |
+
<h3>队列状态</h3>
|
431 |
+
<div class="queue-stats">
|
432 |
+
<div class="stat-item">
|
433 |
+
<div class="stat-value">{queue_info['waiting_tasks']}</div>
|
434 |
+
<div class="stat-label">等待中</div>
|
435 |
+
</div>
|
436 |
+
<div class="stat-item">
|
437 |
+
<div class="stat-value">{queue_info['active_tasks']}</div>
|
438 |
+
<div class="stat-label">处理中</div>
|
439 |
+
</div>
|
440 |
+
<div class="stat-item">
|
441 |
+
<div class="stat-value">{queue_info['worker_threads']}</div>
|
442 |
+
<div class="stat-label">工作线程</div>
|
443 |
+
</div>
|
444 |
+
</div>
|
445 |
|
446 |
+
<div class="wait-time">
|
447 |
+
<p><b>当前预计等待时间:</b> {format_time(queue_info['estimated_wait'])}</p>
|
|
|
448 |
</div>
|
449 |
+
|
450 |
+
<h4>最近处理的任务</h4>
|
451 |
+
<table class="recent-tasks">
|
452 |
+
<tr>
|
453 |
+
<th>任务ID</th>
|
454 |
+
<th>请求时间</th>
|
455 |
+
<th>处理时间</th>
|
456 |
+
</tr>
|
457 |
+
{tasks_html}
|
458 |
+
</table>
|
459 |
</div>
|
460 |
"""
|
461 |
+
|
462 |
+
def ui_get_result(task_id):
|
463 |
+
"""获取任务结果的UI函数
|
464 |
+
|
465 |
+
Args:
|
466 |
+
task_id: 任务ID
|
467 |
+
|
468 |
+
Returns:
|
469 |
+
任务结果
|
470 |
+
"""
|
471 |
+
if not task_id:
|
472 |
+
return None
|
473 |
+
|
474 |
+
status = check_status(task_id)
|
475 |
|
476 |
+
if status['status'] == 'completed':
|
477 |
+
return status.get('result', None)
|
478 |
+
|
479 |
+
return None
|
480 |
|
481 |
+
def launch_workers():
|
482 |
+
"""启动工作线程"""
|
483 |
+
global running
|
484 |
+
running = True
|
485 |
+
|
486 |
+
# 创建工作线程
|
487 |
+
for _ in range(worker_threads):
|
488 |
+
worker = threading.Thread(target=queue_processor)
|
489 |
+
worker.daemon = True
|
490 |
+
worker.start()
|
491 |
|
492 |
+
# 自定义CSS
|
493 |
+
custom_css = """
|
494 |
+
.container {
|
495 |
+
max-width: 1200px;
|
496 |
+
margin: 0 auto;
|
497 |
+
}
|
|
|
|
|
|
|
498 |
|
499 |
+
.status-card, .queue-info-card {
|
500 |
+
background: #f7f7f7;
|
501 |
+
border-radius: 10px;
|
502 |
+
padding: 15px;
|
503 |
+
margin: 10px 0;
|
504 |
+
box-shadow: 0 2px 5px rgba(0,0,0,0.1);
|
505 |
+
}
|
506 |
+
|
507 |
+
.status-card.success {
|
508 |
+
background: #e7f5e7;
|
509 |
+
border-left: 5px solid #28a745;
|
510 |
+
}
|
511 |
+
|
512 |
+
.status-card.error {
|
513 |
+
background: #f8d7da;
|
514 |
+
border-left: 5px solid #dc3545;
|
515 |
+
}
|
516 |
+
|
517 |
+
.error-message {
|
518 |
+
color: #dc3545;
|
519 |
+
font-weight: bold;
|
520 |
+
padding: 10px;
|
521 |
+
background: #f8d7da;
|
522 |
+
border-radius: 5px;
|
523 |
+
}
|
524 |
+
|
525 |
+
.notice {
|
526 |
+
color: #0c5460;
|
527 |
+
background-color: #d1ecf1;
|
528 |
+
padding: 10px;
|
529 |
+
border-radius: 5px;
|
530 |
+
}
|
531 |
+
|
532 |
+
.queue-stats {
|
533 |
+
display: flex;
|
534 |
+
justify-content: space-around;
|
535 |
+
margin: 20px 0;
|
536 |
+
}
|
537 |
+
|
538 |
+
.stat-item {
|
539 |
+
text-align: center;
|
540 |
+
}
|
541 |
+
|
542 |
+
.stat-value {
|
543 |
+
font-size: 24px;
|
544 |
+
font-weight: bold;
|
545 |
+
color: #007bff;
|
546 |
+
}
|
547 |
+
|
548 |
+
.stat-label {
|
549 |
+
color: #6c757d;
|
550 |
+
}
|
551 |
+
|
552 |
+
.wait-time {
|
553 |
+
text-align: center;
|
554 |
+
margin: 15px 0;
|
555 |
+
padding: 10px;
|
556 |
+
background: #e2e3e5;
|
557 |
+
border-radius: 5px;
|
558 |
+
}
|
559 |
+
|
560 |
+
.recent-tasks {
|
561 |
+
width: 100%;
|
562 |
+
border-collapse: collapse;
|
563 |
+
margin-top: 10px;
|
564 |
+
}
|
565 |
+
|
566 |
+
.recent-tasks th, .recent-tasks td {
|
567 |
+
border: 1px solid #dee2e6;
|
568 |
+
padding: 8px;
|
569 |
+
text-align: center;
|
570 |
+
}
|
571 |
+
|
572 |
+
.recent-tasks th {
|
573 |
+
background-color: #e9ecef;
|
574 |
+
}
|
575 |
+
|
576 |
+
.tabs {
|
577 |
+
margin-top: 20px;
|
578 |
+
}
|
579 |
+
"""
|
580 |
+
|
581 |
+
# 初始化并启动工作线程
|
582 |
+
launch_workers()
|
583 |
|
584 |
# 创建Gradio接口
|
585 |
+
with gr.Blocks(css=custom_css) as demo:
|
586 |
+
gr.Markdown("# 代码评估服务")
|
587 |
+
gr.Markdown("支持多种编程语言的代码评估服务,采用队列机制处理请求")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
588 |
|
589 |
+
with gr.Tab("提交任务"):
|
590 |
+
with gr.Row():
|
591 |
+
with gr.Column(scale=2):
|
592 |
+
input_json = gr.JSON(label="输入数据")
|
593 |
+
submit_btn = gr.Button("提交任务", variant="primary")
|
594 |
+
task_id_output = gr.Textbox(label="任务ID", visible=True)
|
595 |
+
status_html = gr.HTML(label="状态")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
596 |
|
597 |
+
with gr.Column(scale=1):
|
598 |
+
queue_info_html = gr.HTML()
|
599 |
+
refresh_queue_btn = gr.Button("刷新队列状态")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
600 |
|
601 |
+
with gr.Tab("查询任务"):
|
602 |
with gr.Row():
|
603 |
+
with gr.Column(scale=1):
|
604 |
+
check_task_id = gr.Textbox(label="任务ID")
|
605 |
+
check_btn = gr.Button("查询状态", variant="primary")
|
606 |
+
get_result_btn = gr.Button("获取结果")
|
|
|
|
|
|
|
607 |
|
608 |
+
with gr.Column(scale=2):
|
609 |
+
check_status_html = gr.HTML()
|
610 |
+
result_output = gr.JSON(label="任务结果")
|
611 |
+
|
612 |
+
# 定义更新函数
|
613 |
+
def update_queue_info():
|
614 |
+
return ui_get_queue_info()
|
615 |
+
|
616 |
+
# 定时更新队列信息
|
617 |
+
demo.load(update_queue_info, None, queue_info_html, every=5)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
618 |
|
619 |
+
# 事件处理
|
620 |
+
submit_btn.click(ui_submit, inputs=[input_json], outputs=[task_id_output, status_html])
|
621 |
+
refresh_queue_btn.click(update_queue_info, None, queue_info_html)
|
622 |
+
check_btn.click(ui_check_status, inputs=[check_task_id], outputs=[check_status_html])
|
623 |
+
get_result_btn.click(ui_get_result, inputs=[check_task_id], outputs=[result_output])
|
624 |
|
625 |
+
# 添加兼容原有接口的评估端点
|
626 |
+
demo.queue()
|
627 |
+
evaluate_endpoint = demo.load(fn=synchronous_evaluate, inputs=gr.JSON(), outputs=gr.JSON(), api_name="evaluate")
|
628 |
+
|
629 |
+
if __name__ == "__main__":
|
630 |
try:
|
631 |
+
demo.launch()
|
632 |
+
finally:
|
633 |
+
# 停止工作线程
|
634 |
+
running = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|