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'') else: html_parts.append(f'') 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'') else: html_parts.append(f'') html_parts.append('') html_parts.append("""
{header}{header}
{value}{value}
""") 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