jutor_write / assignment_ui.py
youngtsai's picture
raise gr.Error("網路塞車,或是內容有誤,請稍後重新嘗試!")
f1b30c1
raw
history blame
26.1 kB
import gradio as gr
def create_assignment_ui(user_data, assignment_service, submission_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="選擇類型")
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="提交截止日期")
assignment_attach_materials = gr.Textbox(label="附件參考", placeholder='[{"type": "video", "url": "link1", "description": "描述"}]')
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']
assignment_create_button.click(
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, 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]
)
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()
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):
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>
<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 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):
submission_data = submission_json.get('submission_data', {})
content = submission_data.get('content', {})
content_type = content.get('content_type', '')
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);">
<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;">🆔 學生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>
"""
if content_type == 'chinese_full_paragraph_evaluation':
html += generate_chinese_full_paragraph_html(content)
elif content_type == 'english_full_paragraph_evaluation':
html += generate_english_full_paragraph_html(content)
else:
html += "<p>不支持的内容类型</p>"
html += """
</div>
</div>
"""
return html
def generate_chinese_full_paragraph_html(content):
html = """
<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 += 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 += generate_table_html(content['chinese_full_paragraph_evaluate_output_table'], "📊 评分结果", "#dc3545")
html += """
</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 += 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 += generate_table_html(content['chinese_full_paragraph_refine_output_table'], "📊 修改后评分", "#20c997")
html += 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 html
def generate_english_full_paragraph_html(content):
html = """
<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 += 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 += generate_table_html(content['full_paragraph_evaluate_output'], "📊 評分結果", "#dc3545")
html += """
</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 += 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 += generate_table_html(content['full_paragraph_correct_grammatical_spelling_errors_output_table'], "📊 修訂文法與拼字錯誤", "#20c997")
html += 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 += generate_table_html(content['full_paragraph_refine_output_table'], "📊 段落改善建議", "#20c997")
html += 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 html
def generate_table_html(table_data, title, color):
if not table_data or not isinstance(table_data, list):
return ""
html = 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; table-layout: fixed;">
<tr style="background-color: #e9ecef;">
"""
# 動態生成表頭
headers = table_data[0].keys()
for header in headers:
if header.lower() == '評分' or header.lower() == 'score':
html += f'<th style="padding: 10px; border: 1px solid #dee2e6; width: 80px;">{header}</th>'
else:
html += f'<th style="padding: 10px; border: 1px solid #dee2e6;">{header}</th>'
html += '</tr>'
# 動態生成表格內容
for item in table_data:
html += '<tr>'
for key, value in item.items():
if key.lower() == '評分' or key.lower() == 'score':
html += f'<td style="padding: 10px; border: 1px solid #dee2e6; width: 80px; text-align: center;">{value}</td>'
else:
html += f'<td style="padding: 10px; border: 1px solid #dee2e6;">{value}</td>'
html += '</tr>'
html += """
</table>
</div>
</div>
"""
return html
def update_submission_data_html(submission_json):
html = generate_submission_html(submission_json)
return gr.update(value=html)
def update_submission_data_html(submission_json):
html = generate_submission_html(submission_json)
return gr.update(value=html)
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]
)
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]
)
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]
)
with gr.Tab("生|模擬繳交"):
with gr.Row():
with gr.Column():
assignment_id_input_student = gr.Textbox(label="作業 ID")
get_assignment_button = gr.Button("獲取作業")
assignment_display = gr.JSON(label="作業內容")
get_assignment_button.click(
assignment_service.get_assignment,
inputs=[assignment_id_input_student],
outputs=assignment_display
)
with gr.Row():
with gr.Column():
student_name_input = gr.Textbox(label="學生姓名")
submission_input = gr.Textbox(label="文字輸入")
submit_button = gr.Button("繳交作業")
submission_status = gr.Textbox(label="繳交狀態", interactive=False)
submit_button.click(
submission_service.submit_assignment,
inputs=[assignment_id_input_student, user_data, student_name_input, submission_input],
outputs=submission_status
)
with gr.Row():
with gr.Column():
load_submissions_button = gr.Button("獲取提交的作業")
submissions_radio = gr.Radio([], label="提交的作業", interactive=True)
submission_display = gr.JSON(label="作業內容")
load_submissions_button.click(
submission_service.update_submission_list,
inputs=[user_data],
outputs=submissions_radio
)
submissions_radio.select(
submission_service.get_submission_from_gcs,
inputs=[submissions_radio],
outputs=submission_display
)
return assignment_interface