Spaces:
Sleeping
Sleeping
delete_file
Browse files- app.py +72 -15
- assignment_service.py +24 -2
- assignment_ui.py +105 -45
- storage_service.py +12 -1
app.py
CHANGED
@@ -1042,6 +1042,32 @@ def get_assignment_content(assignment_id):
|
|
1042 |
return None
|
1043 |
return assignment_data
|
1044 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1045 |
def get_chinese_paragraph_practice_log_session_content(file_name):
|
1046 |
if file_name:
|
1047 |
content = GCS_SERVICE.download_as_string("jutor_logs", file_name)
|
@@ -1055,21 +1081,32 @@ def get_chinese_paragraph_practice_log_session_content(file_name):
|
|
1055 |
chinese_full_paragraph_refine_output_table_history = pd.DataFrame(content_json["chinese_full_paragraph_refine_output_table"]) if "chinese_full_paragraph_refine_output_table" in content_json else pd.DataFrame()
|
1056 |
chinese_full_paragraph_save_output_history = content_json["chinese_full_paragraph_save_output"] if "chinese_full_paragraph_save_output" in content_json else ""
|
1057 |
|
|
|
1058 |
assignment_id = content_json["assignment_id"] if "assignment_id" in content_json else ""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
1059 |
if assignment_id:
|
1060 |
-
assignment = get_assignment_content(assignment_id)
|
1061 |
chinese_assignment_content = gr.update(visible=True)
|
1062 |
-
|
1063 |
-
|
1064 |
-
|
1065 |
-
|
1066 |
-
|
1067 |
-
|
1068 |
-
|
1069 |
-
|
1070 |
-
|
1071 |
-
|
1072 |
-
|
|
|
|
|
|
|
|
|
1073 |
else:
|
1074 |
chinese_full_paragraph_input_history = ""
|
1075 |
chinese_full_paragraph_evaluate_output_text_history = ""
|
@@ -1084,6 +1121,7 @@ def get_chinese_paragraph_practice_log_session_content(file_name):
|
|
1084 |
chinese_assignment_topic = ""
|
1085 |
chinese_assignment_introduction = ""
|
1086 |
chinese_assignment_description = ""
|
|
|
1087 |
|
1088 |
return chinese_full_paragraph_input_history, \
|
1089 |
chinese_full_paragraph_evaluate_output_text_history, \
|
@@ -1096,7 +1134,8 @@ def get_chinese_paragraph_practice_log_session_content(file_name):
|
|
1096 |
chinese_assignment_grade, \
|
1097 |
chinese_assignment_topic, \
|
1098 |
chinese_assignment_introduction, \
|
1099 |
-
chinese_assignment_description
|
|
|
1100 |
|
1101 |
|
1102 |
# === OpenAI Assistant ===
|
@@ -1591,12 +1630,19 @@ def init_params(request: gr.Request):
|
|
1591 |
chinese_assignment_topic = gr.update(value=assignment_json_value.get("metadata", {}).get("topic", ""))
|
1592 |
chinese_assignment_introduction = gr.update(value=assignment_json_value.get("metadata", {}).get("introduction", ""))
|
1593 |
chinese_assignment_description = gr.update(value=assignment_json_value.get("metadata", {}).get("description", ""))
|
|
|
|
|
|
|
|
|
|
|
|
|
1594 |
else:
|
1595 |
chinese_assignment_row = gr.update(visible=False)
|
1596 |
chinese_assignment_grade = gr.update(value="")
|
1597 |
chinese_assignment_topic = gr.update(value="")
|
1598 |
chinese_assignment_introduction = gr.update(value="")
|
1599 |
chinese_assignment_description = gr.update(value="")
|
|
|
1600 |
|
1601 |
assignment_id_input = gr.update(value=assignment_id_value)
|
1602 |
assignment_json = gr.update(value=assignment_json_value)
|
@@ -1607,13 +1653,19 @@ def init_params(request: gr.Request):
|
|
1607 |
chinese_assignment_topic = gr.update(value="")
|
1608 |
chinese_assignment_introduction = gr.update(value="")
|
1609 |
chinese_assignment_description = gr.update(value="")
|
|
|
1610 |
assignment_id_input = gr.update(value=None)
|
1611 |
assignment_json = gr.update(value=None)
|
1612 |
|
1613 |
return user_data, \
|
1614 |
admin_group, session_timestamp, request_origin, \
|
1615 |
assignment_id_input, assignment_json, \
|
1616 |
-
chinese_assignment_row,
|
|
|
|
|
|
|
|
|
|
|
1617 |
english_group, chinese_group, assignment_group
|
1618 |
|
1619 |
CSS = """
|
@@ -3192,6 +3244,7 @@ with gr.Blocks(theme=THEME, css=CSS) as demo:
|
|
3192 |
chinese_assignment_topic = gr.Textbox(label="主題", interactive=False)
|
3193 |
chinese_assignment_introduction = gr.Textbox(label="寫作引文", interactive=False)
|
3194 |
chinese_assignment_description = gr.Textbox(label="作業說明", interactive=False)
|
|
|
3195 |
|
3196 |
# =====中文全文批改=====
|
3197 |
with gr.Tab("中文全文批改") as chinese_full_paragraph_tab:
|
@@ -3462,6 +3515,8 @@ with gr.Blocks(theme=THEME, css=CSS) as demo:
|
|
3462 |
chinese_assignment_introduction_history_log = gr.Markdown()
|
3463 |
gr.Markdown("<span style='color:#4e80ee'>作業說明</span>")
|
3464 |
chinese_assignment_description_history_log = gr.Markdown()
|
|
|
|
|
3465 |
|
3466 |
gr.Markdown("---")
|
3467 |
gr.Markdown("# 回傳作業內容")
|
@@ -3505,7 +3560,8 @@ with gr.Blocks(theme=THEME, css=CSS) as demo:
|
|
3505 |
chinese_assignment_grade_history_log,
|
3506 |
chinese_assignment_topic_history_log,
|
3507 |
chinese_assignment_introduction_history_log,
|
3508 |
-
chinese_assignment_description_history_log
|
|
|
3509 |
]
|
3510 |
)
|
3511 |
|
@@ -3585,6 +3641,7 @@ with gr.Blocks(theme=THEME, css=CSS) as demo:
|
|
3585 |
chinese_assignment_topic,
|
3586 |
chinese_assignment_introduction,
|
3587 |
chinese_assignment_description,
|
|
|
3588 |
english_group,
|
3589 |
chinese_group,
|
3590 |
assignment_group
|
|
|
1042 |
return None
|
1043 |
return assignment_data
|
1044 |
|
1045 |
+
def get_assignment_formatted_deadline(submission_deadline_data):
|
1046 |
+
if submission_deadline_data is None:
|
1047 |
+
return "未設置截止日期"
|
1048 |
+
|
1049 |
+
try:
|
1050 |
+
# 嘗試解析 Unix 時間戳
|
1051 |
+
if isinstance(submission_deadline_data, (int, float)):
|
1052 |
+
submission_datetime = datetime.fromtimestamp(submission_deadline_data)
|
1053 |
+
# 嘗試解析 ISO 格式字串
|
1054 |
+
elif isinstance(submission_deadline_data, str):
|
1055 |
+
try:
|
1056 |
+
submission_datetime = datetime.fromisoformat(submission_deadline_data)
|
1057 |
+
except ValueError:
|
1058 |
+
# 如果不是 ISO 格式,嘗試其他常見格式
|
1059 |
+
submission_datetime = datetime.strptime(submission_deadline_data, "%Y-%m-%d %H:%M:%S")
|
1060 |
+
else:
|
1061 |
+
raise ValueError(f"無效的日期類型: {type(submission_deadline_data)}")
|
1062 |
+
|
1063 |
+
# 確保時間是 UTC+8
|
1064 |
+
taipei_tz = pytz.timezone('Asia/Taipei')
|
1065 |
+
submission_datetime = submission_datetime.astimezone(taipei_tz)
|
1066 |
+
|
1067 |
+
return submission_datetime.strftime("%Y-%m-%d %H:%M")
|
1068 |
+
except Exception as e:
|
1069 |
+
return f"無效的日期格式: {str(e)}"
|
1070 |
+
|
1071 |
def get_chinese_paragraph_practice_log_session_content(file_name):
|
1072 |
if file_name:
|
1073 |
content = GCS_SERVICE.download_as_string("jutor_logs", file_name)
|
|
|
1081 |
chinese_full_paragraph_refine_output_table_history = pd.DataFrame(content_json["chinese_full_paragraph_refine_output_table"]) if "chinese_full_paragraph_refine_output_table" in content_json else pd.DataFrame()
|
1082 |
chinese_full_paragraph_save_output_history = content_json["chinese_full_paragraph_save_output"] if "chinese_full_paragraph_save_output" in content_json else ""
|
1083 |
|
1084 |
+
# 作業模式
|
1085 |
assignment_id = content_json["assignment_id"] if "assignment_id" in content_json else ""
|
1086 |
+
chinese_assignment_content = gr.update(visible=False)
|
1087 |
+
chinese_assignment_grade = ""
|
1088 |
+
chinese_assignment_topic = ""
|
1089 |
+
chinese_assignment_introduction = ""
|
1090 |
+
chinese_assignment_description = ""
|
1091 |
+
chinese_assignment_submission_deadline = ""
|
1092 |
+
|
1093 |
if assignment_id:
|
|
|
1094 |
chinese_assignment_content = gr.update(visible=True)
|
1095 |
+
try:
|
1096 |
+
assignment = get_assignment_content(assignment_id)
|
1097 |
+
chinese_assignment_grade = assignment["metadata"]["grade"]
|
1098 |
+
chinese_assignment_topic = assignment["metadata"]["topic"]
|
1099 |
+
chinese_assignment_introduction = assignment["metadata"]["introduction"]
|
1100 |
+
chinese_assignment_description = assignment["metadata"]["description"]
|
1101 |
+
submission_deadline = assignment["metadata"]["submission_deadline"]
|
1102 |
+
chinese_assignment_submission_deadline = get_assignment_formatted_deadline(submission_deadline)
|
1103 |
+
except Exception as e:
|
1104 |
+
print(f"Error: {e}")
|
1105 |
+
chinese_assignment_grade = "作業已刪除"
|
1106 |
+
chinese_assignment_topic = "作業已刪除"
|
1107 |
+
chinese_assignment_introduction = "作業已刪除"
|
1108 |
+
chinese_assignment_description = "作業已刪除"
|
1109 |
+
chinese_assignment_submission_deadline = "作業已刪除"
|
1110 |
else:
|
1111 |
chinese_full_paragraph_input_history = ""
|
1112 |
chinese_full_paragraph_evaluate_output_text_history = ""
|
|
|
1121 |
chinese_assignment_topic = ""
|
1122 |
chinese_assignment_introduction = ""
|
1123 |
chinese_assignment_description = ""
|
1124 |
+
chinese_assignment_submission_deadline = ""
|
1125 |
|
1126 |
return chinese_full_paragraph_input_history, \
|
1127 |
chinese_full_paragraph_evaluate_output_text_history, \
|
|
|
1134 |
chinese_assignment_grade, \
|
1135 |
chinese_assignment_topic, \
|
1136 |
chinese_assignment_introduction, \
|
1137 |
+
chinese_assignment_description, \
|
1138 |
+
chinese_assignment_submission_deadline
|
1139 |
|
1140 |
|
1141 |
# === OpenAI Assistant ===
|
|
|
1630 |
chinese_assignment_topic = gr.update(value=assignment_json_value.get("metadata", {}).get("topic", ""))
|
1631 |
chinese_assignment_introduction = gr.update(value=assignment_json_value.get("metadata", {}).get("introduction", ""))
|
1632 |
chinese_assignment_description = gr.update(value=assignment_json_value.get("metadata", {}).get("description", ""))
|
1633 |
+
submission_deadline = assignment_json_value.get("metadata", {}).get("submission_deadline", "")
|
1634 |
+
# submission_deadline 轉成 y-m-d H:M
|
1635 |
+
submission_deadline_value = submission_deadline_value = get_assignment_formatted_deadline(submission_deadline)
|
1636 |
+
chinese_assignment_submission_deadline = gr.update(value=submission_deadline_value)
|
1637 |
+
|
1638 |
+
print(f"chinese_assignment_submission_deadline: {chinese_assignment_submission_deadline}")
|
1639 |
else:
|
1640 |
chinese_assignment_row = gr.update(visible=False)
|
1641 |
chinese_assignment_grade = gr.update(value="")
|
1642 |
chinese_assignment_topic = gr.update(value="")
|
1643 |
chinese_assignment_introduction = gr.update(value="")
|
1644 |
chinese_assignment_description = gr.update(value="")
|
1645 |
+
chinese_assignment_submission_deadline = gr.update(value="")
|
1646 |
|
1647 |
assignment_id_input = gr.update(value=assignment_id_value)
|
1648 |
assignment_json = gr.update(value=assignment_json_value)
|
|
|
1653 |
chinese_assignment_topic = gr.update(value="")
|
1654 |
chinese_assignment_introduction = gr.update(value="")
|
1655 |
chinese_assignment_description = gr.update(value="")
|
1656 |
+
chinese_assignment_submission_deadline = gr.update(value="")
|
1657 |
assignment_id_input = gr.update(value=None)
|
1658 |
assignment_json = gr.update(value=None)
|
1659 |
|
1660 |
return user_data, \
|
1661 |
admin_group, session_timestamp, request_origin, \
|
1662 |
assignment_id_input, assignment_json, \
|
1663 |
+
chinese_assignment_row, \
|
1664 |
+
chinese_assignment_grade, \
|
1665 |
+
chinese_assignment_topic, \
|
1666 |
+
chinese_assignment_introduction, \
|
1667 |
+
chinese_assignment_description, \
|
1668 |
+
chinese_assignment_submission_deadline, \
|
1669 |
english_group, chinese_group, assignment_group
|
1670 |
|
1671 |
CSS = """
|
|
|
3244 |
chinese_assignment_topic = gr.Textbox(label="主題", interactive=False)
|
3245 |
chinese_assignment_introduction = gr.Textbox(label="寫作引文", interactive=False)
|
3246 |
chinese_assignment_description = gr.Textbox(label="作業說明", interactive=False)
|
3247 |
+
chinese_assignment_submission_deadline = gr.Textbox(label="截止日期", interactive=False)
|
3248 |
|
3249 |
# =====中文全文批改=====
|
3250 |
with gr.Tab("中文全文批改") as chinese_full_paragraph_tab:
|
|
|
3515 |
chinese_assignment_introduction_history_log = gr.Markdown()
|
3516 |
gr.Markdown("<span style='color:#4e80ee'>作業說明</span>")
|
3517 |
chinese_assignment_description_history_log = gr.Markdown()
|
3518 |
+
gr.Markdown("<span style='color:#4e80ee'>作業繳交截止時間</span>")
|
3519 |
+
chinese_assignment_submission_deadline_history_log = gr.Markdown()
|
3520 |
|
3521 |
gr.Markdown("---")
|
3522 |
gr.Markdown("# 回傳作業內容")
|
|
|
3560 |
chinese_assignment_grade_history_log,
|
3561 |
chinese_assignment_topic_history_log,
|
3562 |
chinese_assignment_introduction_history_log,
|
3563 |
+
chinese_assignment_description_history_log,
|
3564 |
+
chinese_assignment_submission_deadline_history_log
|
3565 |
]
|
3566 |
)
|
3567 |
|
|
|
3641 |
chinese_assignment_topic,
|
3642 |
chinese_assignment_introduction,
|
3643 |
chinese_assignment_description,
|
3644 |
+
chinese_assignment_submission_deadline,
|
3645 |
english_group,
|
3646 |
chinese_group,
|
3647 |
assignment_group
|
assignment_service.py
CHANGED
@@ -19,7 +19,11 @@ class AssignmentService:
|
|
19 |
return assignment_id
|
20 |
|
21 |
def create_assignment_metadata(self, assignment_type, grade, topic, introduction, description, attach_materials, submission_deadline):
|
22 |
-
#
|
|
|
|
|
|
|
|
|
23 |
metadata = {
|
24 |
"assignment_type": assignment_type,
|
25 |
"grade": grade,
|
@@ -135,5 +139,23 @@ class AssignmentService:
|
|
135 |
return gr.update(choices=choices)
|
136 |
|
137 |
|
138 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
139 |
|
|
|
19 |
return assignment_id
|
20 |
|
21 |
def create_assignment_metadata(self, assignment_type, grade, topic, introduction, description, attach_materials, submission_deadline):
|
22 |
+
# submission_deadline is Unix timestamp (seconds)
|
23 |
+
# convert to ISO format with timezone, example: 2024-06-01T00:00:00+08:00
|
24 |
+
taipei_tz = pytz.timezone('Asia/Taipei')
|
25 |
+
submission_deadline = datetime.fromtimestamp(submission_deadline, tz=taipei_tz).isoformat()
|
26 |
+
|
27 |
metadata = {
|
28 |
"assignment_type": assignment_type,
|
29 |
"grade": grade,
|
|
|
139 |
return gr.update(choices=choices)
|
140 |
|
141 |
|
142 |
+
def delete_assignment(self, assignment_id, user_data):
|
143 |
+
try:
|
144 |
+
# 刪除作業文件
|
145 |
+
file_name = f"assignments/{assignment_id}.json"
|
146 |
+
if not self.gcs_service.delete_file(self.bucket_name, file_name):
|
147 |
+
raise Exception("Failed to delete assignment file")
|
148 |
+
|
149 |
+
# 從用戶的作業列表中刪除
|
150 |
+
user_assignments = self.get_user_assignments(user_data)
|
151 |
+
if assignment_id in user_assignments:
|
152 |
+
del user_assignments[assignment_id]
|
153 |
+
encoded_user_id_url = urllib.parse.quote(user_data, safe='')
|
154 |
+
self.gcs_service.upload_json_string(self.bucket_name, f"users/{encoded_user_id_url}/assignments.json", json.dumps(user_assignments))
|
155 |
+
|
156 |
+
return True
|
157 |
+
except Exception as e:
|
158 |
+
print(f"Error deleting assignment: {e}")
|
159 |
+
return False
|
160 |
+
|
161 |
|
assignment_ui.py
CHANGED
@@ -1,4 +1,5 @@
|
|
1 |
import gradio as gr
|
|
|
2 |
|
3 |
def create_assignment_ui(user_data, assignment_service, submission_service):
|
4 |
with gr.Blocks() as assignment_interface:
|
@@ -13,7 +14,7 @@ def create_assignment_ui(user_data, assignment_service, submission_service):
|
|
13 |
"中文寫作 AI 批改",
|
14 |
"英文寫作 AI 批改"
|
15 |
]
|
16 |
-
assignment_type = gr.Radio(choices=assignment_type_list, label="選擇類型")
|
17 |
assignment_grade = gr.Radio(["一年級", "二年級", "三年級", "四年級", "五年級", "六年級"], label="選擇年級", visible=False)
|
18 |
with gr.Row():
|
19 |
gr.Markdown("### 內容規範")
|
@@ -24,7 +25,7 @@ def create_assignment_ui(user_data, assignment_service, submission_service):
|
|
24 |
gr.Markdown("### 繳交規範")
|
25 |
with gr.Row():
|
26 |
assignment_submission_deadline = gr.DateTime(label="提交截止日期")
|
27 |
-
assignment_attach_materials = gr.Textbox(label="附件參考", placeholder='[{"type": "video", "url": "link1", "description": "描述"}]')
|
28 |
with gr.Row():
|
29 |
assignment_create_button = gr.Button("建立作業")
|
30 |
assignment_metadata = gr.JSON(label="作業元數據", visible=False)
|
@@ -73,6 +74,37 @@ def create_assignment_ui(user_data, assignment_service, submission_service):
|
|
73 |
with gr.Column(scale=2):
|
74 |
assignment_data = gr.JSON(label="作業內容", visible=False)
|
75 |
assignment_data_html = gr.HTML()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
76 |
with gr.Row():
|
77 |
with gr.Column(scale=1):
|
78 |
submissions_list_json = gr.JSON(visible=False)
|
@@ -92,6 +124,25 @@ def create_assignment_ui(user_data, assignment_service, submission_service):
|
|
92 |
return assignment_list, assignment_data, assignment_data_html, submissions_list_json, submissions_list_radio, submission_data_json, submission_data_html
|
93 |
|
94 |
def update_assignment_data_html(assignment_data):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
95 |
html = f"""
|
96 |
<div style="background-color: #f8f9fa; padding: 30px; border-radius: 15px; font-family: 'Helvetica', sans-serif;">
|
97 |
<h2 style="color: #007bff; font-size: 28px; margin-bottom: 20px;">✨ 作業詳情</h2>
|
@@ -123,6 +174,11 @@ def create_assignment_ui(user_data, assignment_service, submission_service):
|
|
123 |
<div style="font-weight: bold; color: #17a2b8; margin-bottom: 5px;">🔗 作業連結</div>
|
124 |
<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>
|
125 |
</div>
|
|
|
|
|
|
|
|
|
|
|
126 |
|
127 |
<div>
|
128 |
<div style="font-weight: bold; color: #dc3545; margin-bottom: 5px;">📎 附加材料</div>
|
@@ -133,6 +189,12 @@ def create_assignment_ui(user_data, assignment_service, submission_service):
|
|
133 |
"""
|
134 |
return gr.update(value=html)
|
135 |
|
|
|
|
|
|
|
|
|
|
|
|
|
136 |
def update_submissions_list_radio(submission_ids):
|
137 |
choices = []
|
138 |
for submission_id in submission_ids:
|
@@ -373,6 +435,10 @@ def create_assignment_ui(user_data, assignment_service, submission_service):
|
|
373 |
fn=init_assignment_list_data,
|
374 |
inputs=[],
|
375 |
outputs=[assignment_list, assignment_data, assignment_data_html, submissions_list_json, submissions_list_radio, submission_data_json, submission_data_html]
|
|
|
|
|
|
|
|
|
376 |
)
|
377 |
|
378 |
assignment_list.select(
|
@@ -395,6 +461,10 @@ def create_assignment_ui(user_data, assignment_service, submission_service):
|
|
395 |
fn=update_submissions_list_radio,
|
396 |
inputs=[submissions_list_json],
|
397 |
outputs=[submissions_list_radio]
|
|
|
|
|
|
|
|
|
398 |
)
|
399 |
|
400 |
submissions_list_radio.select(
|
@@ -407,48 +477,38 @@ def create_assignment_ui(user_data, assignment_service, submission_service):
|
|
407 |
outputs=[submission_data_html]
|
408 |
)
|
409 |
|
410 |
-
|
411 |
-
|
412 |
-
|
413 |
-
|
414 |
-
|
415 |
-
|
416 |
-
|
417 |
-
|
418 |
-
|
419 |
-
|
420 |
-
|
421 |
-
|
422 |
-
|
423 |
-
|
424 |
-
|
425 |
-
|
426 |
-
|
427 |
-
|
428 |
-
|
429 |
-
|
430 |
-
|
431 |
-
|
432 |
-
|
433 |
-
|
434 |
-
|
435 |
-
|
436 |
-
|
437 |
-
|
438 |
-
|
439 |
-
|
440 |
-
|
441 |
-
|
442 |
-
|
443 |
-
# submission_service.update_submission_list,
|
444 |
-
# inputs=[user_data],
|
445 |
-
# outputs=submissions_radio
|
446 |
-
# )
|
447 |
-
|
448 |
-
# submissions_radio.select(
|
449 |
-
# submission_service.get_submission_from_gcs,
|
450 |
-
# inputs=[submissions_radio],
|
451 |
-
# outputs=submission_display
|
452 |
-
# )
|
453 |
|
454 |
return assignment_interface
|
|
|
1 |
import gradio as gr
|
2 |
+
from datetime import datetime
|
3 |
|
4 |
def create_assignment_ui(user_data, assignment_service, submission_service):
|
5 |
with gr.Blocks() as assignment_interface:
|
|
|
14 |
"中文寫作 AI 批改",
|
15 |
"英文寫作 AI 批改"
|
16 |
]
|
17 |
+
assignment_type = gr.Radio(choices=assignment_type_list, label="選擇類型", value="中文寫作 AI 批改")
|
18 |
assignment_grade = gr.Radio(["一年級", "二年級", "三年級", "四年級", "五年級", "六年級"], label="選擇年級", visible=False)
|
19 |
with gr.Row():
|
20 |
gr.Markdown("### 內容規範")
|
|
|
25 |
gr.Markdown("### 繳交規範")
|
26 |
with gr.Row():
|
27 |
assignment_submission_deadline = gr.DateTime(label="提交截止日期")
|
28 |
+
assignment_attach_materials = gr.Textbox(label="附件參考", placeholder='[{"type": "video", "url": "link1", "description": "描述"}]', visible=False)
|
29 |
with gr.Row():
|
30 |
assignment_create_button = gr.Button("建立作業")
|
31 |
assignment_metadata = gr.JSON(label="作業元數據", visible=False)
|
|
|
74 |
with gr.Column(scale=2):
|
75 |
assignment_data = gr.JSON(label="作業內容", visible=False)
|
76 |
assignment_data_html = gr.HTML()
|
77 |
+
delete_assignment_button = gr.Button("刪除此作業", visible=False, variant="stop")
|
78 |
+
with gr.Row(visible=False) as check_delete_confirmation_row:
|
79 |
+
delete_assignment_execute_button = gr.Button(visible=True, value="⚠️ 警告:刪除作業將永久移除此作業相關數據。請將確認框打勾即可點擊刪除。", interactive=False)
|
80 |
+
delete_confirm = gr.Checkbox(label="我確認要刪除這個作業", visible=True)
|
81 |
+
delete_warning = gr.Markdown(visible=True)
|
82 |
+
|
83 |
+
def show_delete_confirmation():
|
84 |
+
check_delete_confirmation_row = gr.update(visible=True)
|
85 |
+
return check_delete_confirmation_row
|
86 |
+
|
87 |
+
def init_check_delete_confirmation_row():
|
88 |
+
check_delete_confirmation_row = gr.update(visible=False)
|
89 |
+
delete_confirm = gr.update(value=False)
|
90 |
+
delete_warning = gr.update(value="")
|
91 |
+
return check_delete_confirmation_row, delete_confirm, delete_warning
|
92 |
+
|
93 |
+
def check_delete_confirmation(confirmed):
|
94 |
+
if confirmed:
|
95 |
+
delete_assignment_execute_button = gr.update(interactive=True)
|
96 |
+
else:
|
97 |
+
delete_assignment_execute_button = gr.update(interactive=False)
|
98 |
+
|
99 |
+
return delete_assignment_execute_button
|
100 |
+
|
101 |
+
def delete_assignment(assignment_id, user_data):
|
102 |
+
success = assignment_service.delete_assignment(assignment_id, user_data)
|
103 |
+
if success:
|
104 |
+
return gr.update(value="作業已成功刪除"), gr.update(value=None), gr.update(value=None), gr.update(choices=[]), gr.update(value=None), gr.update(value=None)
|
105 |
+
else:
|
106 |
+
return gr.update(value="刪除作業失敗,請稍後再試"), gr.update(), gr.update(), gr.update(), gr.update(), gr.update()
|
107 |
+
|
108 |
with gr.Row():
|
109 |
with gr.Column(scale=1):
|
110 |
submissions_list_json = gr.JSON(visible=False)
|
|
|
124 |
return assignment_list, assignment_data, assignment_data_html, submissions_list_json, submissions_list_radio, submission_data_json, submission_data_html
|
125 |
|
126 |
def update_assignment_data_html(assignment_data):
|
127 |
+
# 處理 submission_deadline
|
128 |
+
submission_deadline = assignment_data['metadata'].get('submission_deadline')
|
129 |
+
formatted_deadline = "未設置截止日期"
|
130 |
+
|
131 |
+
if submission_deadline is not None:
|
132 |
+
try:
|
133 |
+
if isinstance(submission_deadline, (int, float)):
|
134 |
+
# 假設這是一個 Unix 時間戳(秒)
|
135 |
+
submission_datetime = datetime.fromtimestamp(submission_deadline)
|
136 |
+
elif isinstance(submission_deadline, str):
|
137 |
+
# 嘗試解析 ISO 格式的字串
|
138 |
+
submission_datetime = datetime.fromisoformat(submission_deadline)
|
139 |
+
else:
|
140 |
+
raise ValueError(f"無效的日期類型: {type(submission_deadline)}")
|
141 |
+
|
142 |
+
formatted_deadline = submission_datetime.strftime("%Y-%m-%d %H:%M")
|
143 |
+
except ValueError as e:
|
144 |
+
formatted_deadline = f"無效的日期格式: {str(e)}"
|
145 |
+
|
146 |
html = f"""
|
147 |
<div style="background-color: #f8f9fa; padding: 30px; border-radius: 15px; font-family: 'Helvetica', sans-serif;">
|
148 |
<h2 style="color: #007bff; font-size: 28px; margin-bottom: 20px;">✨ 作業詳情</h2>
|
|
|
174 |
<div style="font-weight: bold; color: #17a2b8; margin-bottom: 5px;">🔗 作業連結</div>
|
175 |
<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>
|
176 |
</div>
|
177 |
+
|
178 |
+
<div style="margin-bottom: 20px;">
|
179 |
+
<div style="font-weight: bold; color: #17a2b8; margin-bottom: 5px;">🔗 作業截止日期</div>
|
180 |
+
<div style="background-color: #f8f9fa; padding: 10px 15px; border-radius: 5px;">{formatted_deadline}</div>
|
181 |
+
</div>
|
182 |
|
183 |
<div>
|
184 |
<div style="font-weight: bold; color: #dc3545; margin-bottom: 5px;">📎 附加材料</div>
|
|
|
189 |
"""
|
190 |
return gr.update(value=html)
|
191 |
|
192 |
+
def show_button():
|
193 |
+
return gr.update(visible=True)
|
194 |
+
|
195 |
+
def hide_button():
|
196 |
+
return gr.update(visible=False)
|
197 |
+
|
198 |
def update_submissions_list_radio(submission_ids):
|
199 |
choices = []
|
200 |
for submission_id in submission_ids:
|
|
|
435 |
fn=init_assignment_list_data,
|
436 |
inputs=[],
|
437 |
outputs=[assignment_list, assignment_data, assignment_data_html, submissions_list_json, submissions_list_radio, submission_data_json, submission_data_html]
|
438 |
+
).then(
|
439 |
+
fn=hide_button,
|
440 |
+
inputs=[],
|
441 |
+
outputs=[delete_assignment_button]
|
442 |
)
|
443 |
|
444 |
assignment_list.select(
|
|
|
461 |
fn=update_submissions_list_radio,
|
462 |
inputs=[submissions_list_json],
|
463 |
outputs=[submissions_list_radio]
|
464 |
+
).then(
|
465 |
+
fn=show_button,
|
466 |
+
inputs=[],
|
467 |
+
outputs=[delete_assignment_button]
|
468 |
)
|
469 |
|
470 |
submissions_list_radio.select(
|
|
|
477 |
outputs=[submission_data_html]
|
478 |
)
|
479 |
|
480 |
+
delete_assignment_button.click(
|
481 |
+
fn=hide_button,
|
482 |
+
inputs=[],
|
483 |
+
outputs=[delete_assignment_button]
|
484 |
+
).then(
|
485 |
+
fn=show_delete_confirmation,
|
486 |
+
inputs=[],
|
487 |
+
outputs=[check_delete_confirmation_row]
|
488 |
+
)
|
489 |
+
|
490 |
+
delete_confirm.change(
|
491 |
+
fn=check_delete_confirmation,
|
492 |
+
inputs=[delete_confirm],
|
493 |
+
outputs=[delete_assignment_execute_button]
|
494 |
+
)
|
495 |
+
|
496 |
+
delete_assignment_execute_button.click(
|
497 |
+
fn=delete_assignment,
|
498 |
+
inputs=[assignment_list, user_data],
|
499 |
+
outputs=[assignment_data_html, assignment_data, submissions_list_json, submissions_list_radio, submission_data_json, submission_data_html]
|
500 |
+
).then(
|
501 |
+
fn=init_check_delete_confirmation_row,
|
502 |
+
inputs=[],
|
503 |
+
outputs=[check_delete_confirmation_row, delete_confirm, delete_warning]
|
504 |
+
).then(
|
505 |
+
fn=hide_button,
|
506 |
+
inputs=[],
|
507 |
+
outputs=[delete_assignment_button]
|
508 |
+
).then(
|
509 |
+
assignment_service.update_assignment_list,
|
510 |
+
inputs=[user_data],
|
511 |
+
outputs=assignment_list
|
512 |
+
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
513 |
|
514 |
return assignment_interface
|
storage_service.py
CHANGED
@@ -56,4 +56,15 @@ class GoogleCloudStorage:
|
|
56 |
|
57 |
def list_files(self, bucket_name, prefix=None):
|
58 |
blobs = self.client.bucket(bucket_name).list_blobs(prefix=prefix)
|
59 |
-
return [blob.name for blob in blobs]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
56 |
|
57 |
def list_files(self, bucket_name, prefix=None):
|
58 |
blobs = self.client.bucket(bucket_name).list_blobs(prefix=prefix)
|
59 |
+
return [blob.name for blob in blobs]
|
60 |
+
|
61 |
+
def delete_file(self, bucket_name, file_name):
|
62 |
+
try:
|
63 |
+
bucket = self.client.bucket(bucket_name)
|
64 |
+
blob = bucket.blob(file_name)
|
65 |
+
blob.delete()
|
66 |
+
print(f"File {file_name} deleted from {bucket_name}")
|
67 |
+
return True
|
68 |
+
except Exception as e:
|
69 |
+
print(f"Error deleting file {file_name} from {bucket_name}: {e}")
|
70 |
+
return False
|