Spaces:
Sleeping
Sleeping
import gradio as gr | |
from datetime import datetime | |
import pandas as pd | |
def create_assignment_ui(user_data, user_email, user_nickname, assignment_service, submission_service, dashboard_service): | |
with gr.Blocks() as assignment_interface: | |
with gr.Tab("老師|建立作業"): | |
with gr.Row(): | |
with gr.Column(): | |
with gr.Row(): | |
gr.Markdown("### 作業資訊") | |
with gr.Row(): | |
assignment_topic = gr.Textbox(label="題目(必填)") | |
assignment_type_list = [ | |
"中文寫作 AI 批改", | |
"英文寫作 AI 批改" | |
] | |
assignment_type = gr.Radio(choices=assignment_type_list, label="選擇類型", value="中文寫作 AI 批改") | |
assignment_grade = gr.Radio(["一年級", "二年級", "三年級", "四年級", "五年級", "六年級"], label="選擇年級", visible=False) | |
with gr.Row(): | |
gr.Markdown("### 內容規範") | |
with gr.Row(): | |
assignment_introduction = gr.TextArea(label="寫作引文") | |
assignment_description = gr.TextArea(label="作業說明") | |
with gr.Row(): | |
gr.Markdown("### 繳交規範") | |
with gr.Row(): | |
assignment_submission_deadline = gr.DateTime(label="提交截止日期", value=None) | |
assignment_attach_materials = gr.Textbox(label="附件參考", placeholder='[{"type": "video", "url": "link1", "description": "描述"}]', visible=False) | |
with gr.Row(): | |
assignment_create_button = gr.Button("建立作業") | |
assignment_metadata = gr.JSON(label="作業元數據", visible=False) | |
assignment_data = gr.JSON(label="作業數據", visible=False) | |
assignment_url = gr.Textbox(label="作業指派連結", interactive=False, show_copy_button=True) | |
assignment_id_display_teacher = gr.Textbox(label="作業 ID", interactive=False, visible=False) | |
def update_gr_assignment_url(assignment_data): | |
return assignment_data['assignment_url'] | |
def update_gr_assignment_id(assignment_data): | |
return assignment_data['assignment_id'] | |
def disable_btn(): | |
return gr.update(interactive=False) | |
def enable_btn(): | |
return gr.update(interactive=True) | |
assignment_create_button.click( | |
fn=disable_btn, | |
inputs=[], | |
outputs=[assignment_create_button] | |
).then( | |
assignment_service.create_assignment_metadata, | |
inputs=[ | |
assignment_type, | |
assignment_grade, | |
assignment_topic, | |
assignment_introduction, | |
assignment_description, | |
assignment_attach_materials, | |
assignment_submission_deadline | |
], | |
outputs=[assignment_metadata] | |
).then( | |
assignment_service.create_assignment, | |
inputs=[user_data, user_email, user_nickname, assignment_type, assignment_metadata], | |
outputs=[assignment_data] | |
).then( | |
update_gr_assignment_url, | |
inputs=[assignment_data], | |
outputs=[assignment_url] | |
).then( | |
update_gr_assignment_id, | |
inputs=[assignment_data], | |
outputs=[assignment_id_display_teacher] | |
).then( | |
# enable | |
fn=enable_btn, | |
inputs=[], | |
outputs=[assignment_create_button] | |
) | |
with gr.Tab("老師|作業列表") as assignment_list_tab: | |
with gr.Row(): | |
with gr.Column(scale=1): | |
get_all_assignments_button = gr.Button("獲取所有作業") | |
assignment_list = gr.Radio([], label="作業列表", interactive=True) | |
with gr.Column(scale=2): | |
assignment_data = gr.JSON(label="作業內容", visible=False) | |
assignment_data_html = gr.HTML() | |
delete_assignment_button = gr.Button("刪除此作業", visible=False, variant="stop") | |
with gr.Row(visible=False) as check_delete_confirmation_row: | |
delete_assignment_execute_button = gr.Button(visible=True, value="⚠️ 警告:刪除作業將永久移除此作業相關數據。請將確認框打勾即可點擊刪除。", interactive=False) | |
delete_confirm = gr.Checkbox(label="我確認要刪除這個作業", visible=True) | |
delete_warning = gr.Markdown(visible=True) | |
def show_delete_confirmation(): | |
check_delete_confirmation_row = gr.update(visible=True) | |
return check_delete_confirmation_row | |
def init_check_delete_confirmation_row(): | |
check_delete_confirmation_row = gr.update(visible=False) | |
delete_confirm = gr.update(value=False) | |
delete_warning = gr.update(value="") | |
return check_delete_confirmation_row, delete_confirm, delete_warning | |
def check_delete_confirmation(confirmed): | |
if confirmed: | |
delete_assignment_execute_button = gr.update(interactive=True) | |
else: | |
delete_assignment_execute_button = gr.update(interactive=False) | |
return delete_assignment_execute_button | |
def delete_assignment(assignment_id, user_data): | |
success = assignment_service.delete_assignment(assignment_id, user_data) | |
if success: | |
return gr.update(value="作業已成功刪除"), gr.update(value=None), gr.update(value=None), gr.update(choices=[]), gr.update(value=None), gr.update(value=None) | |
else: | |
return gr.update(value="刪除作業失敗,請稍後再試"), gr.update(), gr.update(), gr.update(), gr.update(), gr.update() | |
with gr.Row(): | |
with gr.Column(scale=1): | |
submissions_list_json = gr.JSON(visible=False) | |
submissions_list_radio = gr.Radio([], label="已提交的作業學生", interactive=True) | |
with gr.Column(scale=2): | |
submission_data_json = gr.JSON(label="提交內容", visible=False) | |
submission_data_html = gr.HTML() | |
def init_assignment_list_data(assignment_list_value=None): | |
assignment_list = gr.update(value=assignment_list_value) | |
assignment_data = gr.update(value=None) | |
assignment_data_html = gr.update(value=None) | |
submissions_list_json = gr.update(value=None) | |
submissions_list_radio = gr.update(choices=[], value=None) | |
submission_data_json = gr.update(value=None) | |
submission_data_html = gr.update(value=None) | |
return assignment_list, assignment_data, assignment_data_html, submissions_list_json, submissions_list_radio, submission_data_json, submission_data_html | |
def update_assignment_data_html(assignment_data): | |
# check metadata in assignment_data | |
metadata = assignment_data.get('metadata', {}) | |
if metadata: | |
# 處理 submission_deadline | |
submission_deadline = assignment_data['metadata'].get('submission_deadline') | |
formatted_deadline = "未設置截止日期" | |
else: | |
submission_deadline = None | |
formatted_deadline = "未設置截止日期" | |
if submission_deadline is not None: | |
try: | |
if isinstance(submission_deadline, (int, float)): | |
# 假設這是個 Unix 時間戳(秒) | |
submission_datetime = datetime.fromtimestamp(submission_deadline) | |
elif isinstance(submission_deadline, str): | |
# 嘗試解析 ISO 格式的字串 | |
submission_datetime = datetime.fromisoformat(submission_deadline) | |
else: | |
raise ValueError(f"無效的日期類型: {type(submission_deadline)}") | |
formatted_deadline = submission_datetime.strftime("%Y-%m-%d %H:%M") | |
except ValueError as e: | |
formatted_deadline = f"無效的日期格式: {str(e)}" | |
html = f""" | |
<div style="background-color: #f8f9fa; padding: 30px; border-radius: 15px; font-family: 'Helvetica', sans-serif;"> | |
<h2 style="color: #007bff; font-size: 28px; margin-bottom: 20px;">✨ 作業詳情</h2> | |
<div style="background-color: white; border-radius: 10px; padding: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.05);"> | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #6c757d; margin-bottom: 5px;">🏷️ 作業類型</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px;">{assignment_data['assignment_type']}</div> | |
</div> | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #6c757d; margin-bottom: 5px;">📝 作業題目</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px;">{assignment_data['metadata']['topic']}</div> | |
</div> | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #6c757d; margin-bottom: 5px;">🗓️ 日期</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px;">{assignment_data['timestamp']}</div> | |
</div> | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #28a745; margin-bottom: 5px;">📖 寫作引文</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px;">{assignment_data['metadata']['introduction']}</div> | |
</div> | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #ffc107; margin-bottom: 5px;">ℹ️ 作業說明</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px;">{assignment_data['metadata']['description']}</div> | |
</div> | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #17a2b8; margin-bottom: 5px;">🔗 作業連結</div> | |
<a href="{assignment_data['assignment_url']}" target="_blank" style="display: inline-block; padding: 10px 15px; background-color: #007bff; color: white; text-decoration: none; border-radius: 5px; transition: background-color 0.3s;" onmouseover="this.style.backgroundColor='#0056b3'" onmouseout="this.style.backgroundColor='#007bff'">點擊前往作業</a> | |
</div> | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #17a2b8; margin-bottom: 5px;">🔗 作業截止日期</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px;">{formatted_deadline}</div> | |
</div> | |
<div> | |
<div style="font-weight: bold; color: #dc3545; margin-bottom: 5px;">📎 附加材料</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px;">{assignment_data['metadata']['attach_materials']}</div> | |
</div> | |
</div> | |
</div> | |
""" | |
return gr.update(value=html) | |
def show_button(): | |
return gr.update(visible=True) | |
def hide_button(): | |
return gr.update(visible=False) | |
def update_submissions_list_radio(submission_ids): | |
choices = [] | |
for submission_id in submission_ids: | |
submission_data = submission_service.get_submission_from_gcs(submission_id) | |
choice_text = f"{submission_data['student_name']}" | |
choice = (choice_text, submission_id) | |
choices.append(choice) | |
return gr.update(choices=choices) | |
def generate_submission_html(submission_json): | |
html_parts = [] | |
html_parts.append(generate_student_info_html(submission_json)) | |
submission_data = submission_json.get('submission_data', {}) | |
content = submission_data.get('content', {}) | |
content_type = content.get('content_type', '') | |
if content_type == 'chinese_full_paragraph_evaluation': | |
html_parts.append(generate_chinese_full_paragraph_html(content)) | |
elif content_type == 'english_full_paragraph_evaluation': | |
html_parts.append(generate_english_full_paragraph_html(content)) | |
else: | |
html_parts.append("<p>不支持的内容类型</p>") | |
# 添加外层结构的结束标签 | |
html_parts.append(""" | |
</div> | |
</div> | |
""") | |
return ''.join(html_parts) | |
def generate_student_info_html(submission_json): | |
return f""" | |
<details> | |
<summary style="cursor: pointer; font-weight: bold; color: #6c757d; padding: 10px; background-color: #e9ecef; border-radius: 5px;">學生資訊</summary> | |
<div style="margin-top: 10px;"> | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #6c757d; margin-bottom: 5px;">👤 學生姓名</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px;"> | |
{submission_json.get('student_name', '未提供')} | |
</div> | |
</div> | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #6c757d; margin-bottom: 5px;">📧 學生Email</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px;"> | |
{submission_json.get('student_email', '未提供')} | |
</div> | |
</div> | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #6c757d; margin-bottom: 5px;">🆔 學生ID</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px;"> | |
{submission_json.get('student_id', '未提供')} | |
</div> | |
</div> | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #6c757d; margin-bottom: 5px;">🕒 繳交時間</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px;">{submission_json.get('timestamp', '未提供')}</div> | |
</div> | |
</div> | |
</details> | |
""" | |
def generate_chinese_full_paragraph_html(content): | |
html_parts = [] | |
html_parts.append(""" | |
<details open> | |
<summary style="cursor: pointer; font-weight: bold; color: #28a745; padding: 10px; background-color: #e9ecef; border-radius: 5px; margin-top: 20px;">原文評估</summary> | |
<div style="margin-top: 10px;"> | |
""") | |
html_parts.append(f""" | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #28a745; margin-bottom: 5px;">📄 輸入段落全文</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px; white-space: pre-wrap;">{content.get('chinese_full_paragraph_input', '未提供')}</div> | |
</div> | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #17a2b8; margin-bottom: 5px;">📊 段落全文分析</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px; white-space: pre-wrap;">{content.get('chinese_full_paragraph_evaluate_output_text', '未提供')}</div> | |
</div> | |
""") | |
if 'chinese_full_paragraph_evaluate_output_table' in content: | |
html_parts.append(generate_table_html(content['chinese_full_paragraph_evaluate_output_table'], "📊 評分结果", "#dc3545")) | |
html_parts.append(""" | |
</div> | |
</details> | |
<details> | |
<summary style="cursor: pointer; font-weight: bold; color: #6610f2; padding: 10px; background-color: #e9ecef; border-radius: 5px; margin-top: 20px;">第二次修改</summary> | |
<div style="margin-top: 10px;"> | |
""") | |
html_parts.append(f""" | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #6610f2; margin-bottom: 5px;">🔄 段落改善建議 輸入</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px; white-space: pre-wrap;">{content.get('chinese_full_paragraph_refine_input', '未提供')}</div> | |
</div> | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #fd7e14; margin-bottom: 5px;">🔄 段落改善建議</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px; white-space: pre-wrap;">{content.get('chinese_full_paragraph_refine_output_text', '未提供')}</div> | |
</div> | |
""") | |
if 'chinese_full_paragraph_refine_output_table' in content: | |
html_parts.append(generate_table_html(content['chinese_full_paragraph_refine_output_table'], "📊 修改後評分", "#20c997")) | |
html_parts.append(f""" | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #20c997; margin-bottom: 5px;">📝 修改结果</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px; white-space: pre-wrap;">{content.get('chinese_full_paragraph_save_output', '未提供')}</div> | |
</div> | |
</div> | |
</details> | |
""") | |
return ''.join(html_parts) | |
def generate_english_full_paragraph_html(content): | |
html_parts = [] | |
html_parts.append(""" | |
<details open> | |
<summary style="cursor: pointer; font-weight: bold; color: #28a745; padding: 10px; background-color: #e9ecef; border-radius: 5px; margin-top: 20px;">原文</summary> | |
<div style="margin-top: 10px;"> | |
""") | |
html_parts.append(f""" | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #28a745; margin-bottom: 5px;">📄 輸入段落全文</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px; white-space: pre-wrap;">{content.get('full_paragraph_input', '未提供')}</div> | |
</div> | |
""") | |
if 'full_paragraph_evaluate_output' in content: | |
html_parts.append(generate_table_html(content['full_paragraph_evaluate_output'], "📊 評分結果", "#dc3545")) | |
html_parts.append(""" | |
</div> | |
</details> | |
<details> | |
<summary style="cursor: pointer; font-weight: bold; color: #6610f2; padding: 10px; background-color: #e9ecef; border-radius: 5px; margin-top: 20px;">修訂結果</summary> | |
<div style="margin-top: 10px;"> | |
""") | |
html_parts.append(f""" | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #6610f2; margin-bottom: 5px;">🔄 修訂文法與拼字錯誤 輸入</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px; white-space: pre-wrap;">{content.get('full_paragraph_correct_grammatical_spelling_errors_input', '未提供')}</div> | |
</div> | |
""") | |
if 'full_paragraph_correct_grammatical_spelling_errors_output_table' in content: | |
html_parts.append(generate_table_html(content['full_paragraph_correct_grammatical_spelling_errors_output_table'], "📊 修訂文法與拼字錯誤", "#20c997")) | |
html_parts.append(f""" | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #6610f2; margin-bottom: 5px;">🔄 段落改善建議 輸入</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px; white-space: pre-wrap;">{content.get('full_paragraph_refine_input', '未提供')}</div> | |
</div> | |
""") | |
if 'full_paragraph_refine_output_table' in content: | |
html_parts.append(generate_table_html(content['full_paragraph_refine_output_table'], "📊 段落改善建議", "#20c997")) | |
html_parts.append(f""" | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #fd7e14; margin-bottom: 5px;">🔄 修改建議</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px; white-space: pre-wrap;">{content.get('full_paragraph_refine_output', '未提供')}</div> | |
</div> | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: #20c997; margin-bottom: 5px;">📝 修改結果</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px; white-space: pre-wrap;">{content.get('full_paragraph_save_output', '未提供')}</div> | |
</div> | |
</div> | |
</details> | |
""") | |
return ''.join(html_parts) | |
def generate_table_html(table_data, title, color): | |
if not table_data or not isinstance(table_data, list): | |
return "" | |
html_parts = [] | |
html_parts.append(f""" | |
<div style="margin-bottom: 20px;"> | |
<div style="font-weight: bold; color: {color}; margin-bottom: 5px;">{title}</div> | |
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px; overflow-x: auto;"> | |
<table style="width: 100%; border-collapse: collapse; white-space: nowrap;"> | |
<tr style="background-color: #e9ecef;"> | |
""") | |
# 动态生成表头 | |
headers = table_data[0].keys() | |
for header in headers: | |
if header.lower() == '評分' or header.lower() == 'score': | |
html_parts.append(f'<th style="padding: 10px; border: 1px solid #dee2e6; min-width: 80px;">{header}</th>') | |
else: | |
html_parts.append(f'<th style="padding: 10px; border: 1px solid #dee2e6;">{header}</th>') | |
html_parts.append('</tr>') | |
# 动态生成表格内容 | |
for item in table_data: | |
html_parts.append('<tr>') | |
for key, value in item.items(): | |
if key.lower() == '評分' or key.lower() == 'score': | |
html_parts.append(f'<td style="padding: 10px; border: 1px solid #dee2e6; min-width: 80px; text-align: center;">{value}</td>') | |
else: | |
html_parts.append(f'<td style="padding: 10px; border: 1px solid #dee2e6;">{value}</td>') | |
html_parts.append('</tr>') | |
html_parts.append(""" | |
</table> | |
</div> | |
</div> | |
""") | |
return ''.join(html_parts) | |
def update_submission_data_html(submission_json): | |
html = generate_submission_html(submission_json) | |
return gr.update(value=html) | |
get_all_assignments_button.click( | |
assignment_service.update_assignment_list, | |
inputs=[user_data], | |
outputs=assignment_list | |
).then( | |
fn=init_assignment_list_data, | |
inputs=[], | |
outputs=[assignment_list, assignment_data, assignment_data_html, submissions_list_json, submissions_list_radio, submission_data_json, submission_data_html] | |
).then( | |
fn=hide_button, | |
inputs=[], | |
outputs=[delete_assignment_button] | |
) | |
assignment_list.select( | |
fn=init_assignment_list_data, | |
inputs=[assignment_list], | |
outputs=[assignment_list, assignment_data, assignment_data_html, submissions_list_json, submissions_list_radio, submission_data_json, submission_data_html] | |
).then( | |
assignment_service.get_assignment, | |
inputs=[assignment_list], | |
outputs=[assignment_data] | |
).then( | |
fn=update_assignment_data_html, | |
inputs=[assignment_data], | |
outputs=[assignment_data_html] | |
).then( | |
assignment_service.get_assignment_submissions, | |
inputs=[assignment_list], | |
outputs=[submissions_list_json] | |
).then( | |
fn=update_submissions_list_radio, | |
inputs=[submissions_list_json], | |
outputs=[submissions_list_radio] | |
).then( | |
fn=show_button, | |
inputs=[], | |
outputs=[delete_assignment_button] | |
) | |
submissions_list_radio.select( | |
submission_service.get_submission_from_gcs, | |
inputs=[submissions_list_radio], | |
outputs=submission_data_json | |
).then( | |
fn=update_submission_data_html, | |
inputs=[submission_data_json], | |
outputs=[submission_data_html] | |
) | |
delete_assignment_button.click( | |
fn=hide_button, | |
inputs=[], | |
outputs=[delete_assignment_button] | |
).then( | |
fn=show_delete_confirmation, | |
inputs=[], | |
outputs=[check_delete_confirmation_row] | |
) | |
delete_confirm.change( | |
fn=check_delete_confirmation, | |
inputs=[delete_confirm], | |
outputs=[delete_assignment_execute_button] | |
) | |
delete_assignment_execute_button.click( | |
fn=delete_assignment, | |
inputs=[assignment_list, user_data], | |
outputs=[assignment_data_html, assignment_data, submissions_list_json, submissions_list_radio, submission_data_json, submission_data_html] | |
).then( | |
fn=init_check_delete_confirmation_row, | |
inputs=[], | |
outputs=[check_delete_confirmation_row, delete_confirm, delete_warning] | |
).then( | |
fn=hide_button, | |
inputs=[], | |
outputs=[delete_assignment_button] | |
).then( | |
assignment_service.update_assignment_list, | |
inputs=[user_data], | |
outputs=assignment_list | |
) | |
with gr.Tab("儀表板") as dashboard_tab: | |
with gr.Row(): | |
refresh_dashboard_button = gr.Button("刷新儀表板") | |
with gr.Row(): | |
assignments_df = gr.Dataframe( | |
label="作業列表", | |
wrap=True, | |
interactive=False, | |
) | |
with gr.Row(): | |
download_assignments_button = gr.Button("下載作業列表 CSV") | |
assignments_df_file = gr.File() | |
with gr.Row(): | |
gr.HTML(""" | |
<div style="background-color: #f8f9fa; padding: 30px; border-radius: 15px; font-family: 'Helvetica', sans-serif;"> | |
<h2 style="color: #007bff; font-size: 28px; margin-bottom: 20px;">📝 學生回傳作業</h2> | |
<div style="background-color: #e9ecef; padding: 15px; border-radius: 10px; margin-bottom: 20px;"> | |
<span style="color:#4e80ee; font-weight: bold;">提醒:</span> | |
<span>評分已獲得綠燈 🟢 的同學,請老師聯繫樂寫公益學習網(cooldeark@gmail.com)以建立帳號</span> | |
</div> | |
<div style="background-color: white; border-radius: 10px; padding: 20px; box-shadow: 0 2px 4px rgba(0,0,0,0.05);"> | |
""") | |
with gr.Row(): | |
submissions_df = gr.Dataframe( | |
label="所有提交的作業", | |
wrap=True, | |
interactive=False, | |
) | |
with gr.Row(): | |
download_submissions_button = gr.Button("下載提交作業 CSV") | |
submissions_df_file = gr.File() | |
def update_dashboard(user_data, user_email, user_nickname): | |
# 獲取作業列表 | |
assignments = dashboard_service.get_dashboard_data(user_data) | |
assignments_df = pd.DataFrame(assignments) | |
# 獲取所有提交的作業 | |
submissions = dashboard_service.get_all_submissions(user_data, user_email, user_nickname) | |
submissions_df = pd.DataFrame(submissions) | |
return assignments_df, submissions_df | |
def download_df_to_csv(df): | |
import uuid | |
unique_filename = str(uuid.uuid4()) | |
file_path = f"/tmp/{unique_filename}.csv" | |
df.to_csv(file_path, index=False) | |
return file_path | |
refresh_dashboard_button.click( | |
fn=update_dashboard, | |
inputs=[user_data, user_email, user_nickname], | |
outputs=[assignments_df, submissions_df] | |
) | |
download_assignments_button.click( | |
fn=download_df_to_csv, | |
inputs=[assignments_df], | |
outputs=[assignments_df_file] | |
) | |
download_submissions_button.click( | |
fn=download_df_to_csv, | |
inputs=[submissions_df], | |
outputs=[submissions_df_file] | |
) | |
return assignment_interface |