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"""
✨ 作業詳情
🏷️ 作業類型
{assignment_data['assignment_type']}
📝 作業題目
{assignment_data['metadata']['topic']}
🗓️ 日期
{assignment_data['timestamp']}
📖 寫作引文
{assignment_data['metadata']['introduction']}
ℹ️ 作業說明
{assignment_data['metadata']['description']}
🔗 作業截止日期
{formatted_deadline}
📎 附加材料
{assignment_data['metadata']['attach_materials']}
"""
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("不支持的内容类型
")
# 添加外层结构的结束标签
html_parts.append("""
""")
return ''.join(html_parts)
def generate_student_info_html(submission_json):
return f"""
學生資訊
👤 學生姓名
{submission_json.get('student_name', '未提供')}
📧 學生Email
{submission_json.get('student_email', '未提供')}
🆔 學生ID
{submission_json.get('student_id', '未提供')}
🕒 繳交時間
{submission_json.get('timestamp', '未提供')}
"""
def generate_chinese_full_paragraph_html(content):
html_parts = []
html_parts.append("""
原文評估
""")
html_parts.append(f"""
📄 輸入段落全文
{content.get('chinese_full_paragraph_input', '未提供')}
📊 段落全文分析
{content.get('chinese_full_paragraph_evaluate_output_text', '未提供')}
""")
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("""
第二次修改
""")
html_parts.append(f"""
🔄 段落改善建議 輸入
{content.get('chinese_full_paragraph_refine_input', '未提供')}
🔄 段落改善建議
{content.get('chinese_full_paragraph_refine_output_text', '未提供')}
""")
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"""
📝 修改结果
{content.get('chinese_full_paragraph_save_output', '未提供')}
""")
return ''.join(html_parts)
def generate_english_full_paragraph_html(content):
html_parts = []
html_parts.append("""
原文
""")
html_parts.append(f"""
📄 輸入段落全文
{content.get('full_paragraph_input', '未提供')}
""")
if 'full_paragraph_evaluate_output' in content:
html_parts.append(generate_table_html(content['full_paragraph_evaluate_output'], "📊 評分結果", "#dc3545"))
html_parts.append("""
修訂結果
""")
html_parts.append(f"""
🔄 修訂文法與拼字錯誤 輸入
{content.get('full_paragraph_correct_grammatical_spelling_errors_input', '未提供')}
""")
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"""
🔄 段落改善建議 輸入
{content.get('full_paragraph_refine_input', '未提供')}
""")
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"""
🔄 修改建議
{content.get('full_paragraph_refine_output', '未提供')}
📝 修改結果
{content.get('full_paragraph_save_output', '未提供')}
""")
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"""
{title}
""")
# 动态生成表头
headers = table_data[0].keys()
for header in headers:
if header.lower() == '評分' or header.lower() == 'score':
html_parts.append(f'{header} | ')
else:
html_parts.append(f'{header} | ')
html_parts.append('
')
# 动态生成表格内容
for item in table_data:
html_parts.append('')
for key, value in item.items():
if key.lower() == '評分' or key.lower() == 'score':
html_parts.append(f'{value} | ')
else:
html_parts.append(f'{value} | ')
html_parts.append('
')
html_parts.append("""
""")
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("""
📝 學生回傳作業
提醒:
評分已獲得綠燈 🟢 的同學,請老師聯繫樂寫公益學習網(cooldeark@gmail.com)以建立帳號
""")
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