Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,160 +1,10 @@
|
|
| 1 |
import json
|
| 2 |
import time
|
| 3 |
-
from typing import Dict, Any
|
| 4 |
import gradio as gr
|
| 5 |
-
import tempfile
|
| 6 |
-
import os
|
| 7 |
-
import docx
|
| 8 |
-
from docx.shared import Pt, Inches
|
| 9 |
-
from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
|
| 10 |
-
|
| 11 |
-
# --- DOCX Generation Helpers ---
|
| 12 |
-
# (這部分是新增的,用於將 JSON 轉換為 .docx)
|
| 13 |
-
|
| 14 |
-
def set_font_style(run, size_pt=12, bold=False, lang_en="Times New Roman", lang_zh="新細明體"):
|
| 15 |
-
"""設定 Word 文件中的字型樣式 (中英混合)"""
|
| 16 |
-
run.font.size = Pt(size_pt)
|
| 17 |
-
run.font.bold = bold
|
| 18 |
-
run.font.name = lang_en
|
| 19 |
-
# 設定東亞語言字型
|
| 20 |
-
run.font.element.rPr.rFonts.set(docx.oxml.ns.qn('w:eastAsia'), lang_zh)
|
| 21 |
-
|
| 22 |
-
|
| 23 |
-
def generate_admin_docx(data: Dict[str, Any]) -> str:
|
| 24 |
-
"""根據 JSON 數據生成「會議記錄」.docx 文件"""
|
| 25 |
-
try:
|
| 26 |
-
doc = docx.Document()
|
| 27 |
-
|
| 28 |
-
# 1. 標題
|
| 29 |
-
title = doc.add_heading(data.get("文件類型 (Document Type)", "會議記錄"), level=1)
|
| 30 |
-
set_font_style(title.runs[0], size_pt=18, bold=True, lang_zh="標楷體")
|
| 31 |
-
title.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
| 32 |
-
|
| 33 |
-
# 2. 會議資訊
|
| 34 |
-
info = data.get("meeting_info", {})
|
| 35 |
-
doc.add_heading(info.get("topic", "會議主題"), level=2)
|
| 36 |
-
doc.add_paragraph(f"日期 (Date): {info.get('date', 'N/A')}")
|
| 37 |
-
doc.add_paragraph(f"地點 (Location): {info.get('location', 'N/A')}")
|
| 38 |
-
doc.add_paragraph() # Spacing
|
| 39 |
-
|
| 40 |
-
# 3. 出席人員
|
| 41 |
-
doc.add_heading("出席人員 (Attendees)", level=3)
|
| 42 |
-
for attendee in data.get("attendees", ["N/A"]):
|
| 43 |
-
doc.add_paragraph(attendee, style='List Bullet')
|
| 44 |
-
|
| 45 |
-
# 4. 會議重點
|
| 46 |
-
doc.add_heading("會議重點 (Key Points)", level=3)
|
| 47 |
-
for point in data.get("key_points", ["N/A"]):
|
| 48 |
-
doc.add_paragraph(point, style='List Number')
|
| 49 |
-
|
| 50 |
-
# 5. 決議事項 (表格)
|
| 51 |
-
doc.add_heading("決議事項 (Resolutions)", level=3)
|
| 52 |
-
resolutions = data.get("resolutions", [])
|
| 53 |
-
if resolutions:
|
| 54 |
-
table = doc.add_table(rows=1, cols=3)
|
| 55 |
-
table.style = 'Table Grid'
|
| 56 |
-
hdr_cells = table.rows[0].cells
|
| 57 |
-
set_font_style(hdr_cells[0].paragraphs[0].add_run('項目 (Item)'), bold=True)
|
| 58 |
-
set_font_style(hdr_cells[1].paragraphs[0].add_run('負責人 (Responsible)'), bold=True)
|
| 59 |
-
set_font_style(hdr_cells[2].paragraphs[0].add_run('期限 (Deadline)'), bold=True)
|
| 60 |
-
|
| 61 |
-
for item in resolutions:
|
| 62 |
-
row_cells = table.add_row().cells
|
| 63 |
-
row_cells[0].text = item.get("item", "")
|
| 64 |
-
row_cells[1].text = item.get("responsible", "")
|
| 65 |
-
row_cells[2].text = item.get("deadline", "")
|
| 66 |
-
else:
|
| 67 |
-
doc.add_paragraph("無")
|
| 68 |
-
|
| 69 |
-
# 6. 註記
|
| 70 |
-
doc.add_paragraph()
|
| 71 |
-
set_font_style(doc.add_paragraph(data.get("audit_note", "")).add_run(), size_pt=10)
|
| 72 |
-
|
| 73 |
-
# 儲存到暫存檔案
|
| 74 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".docx") as tmp:
|
| 75 |
-
doc.save(tmp.name)
|
| 76 |
-
return tmp.name
|
| 77 |
-
|
| 78 |
-
except Exception as e:
|
| 79 |
-
print(f"Error generating admin DOCX: {e}")
|
| 80 |
-
return None # 返回 None 表示失敗
|
| 81 |
-
|
| 82 |
-
def generate_lesson_plan_docx(data: Dict[str, Any]) -> str:
|
| 83 |
-
"""根據 JSON 數據生成「教案」.docx 文件"""
|
| 84 |
-
try:
|
| 85 |
-
doc = docx.Document()
|
| 86 |
-
|
| 87 |
-
# 1. 標題
|
| 88 |
-
title = doc.add_heading(data.get("lesson_plan_title", "教案標題"), level=1)
|
| 89 |
-
set_font_style(title.runs[0], size_pt=18, bold=True, lang_zh="標楷體")
|
| 90 |
-
title.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
|
| 91 |
-
|
| 92 |
-
doc.add_paragraph(f"適用年級 (Grade Level): {data.get('grade_level', 'N/A')}")
|
| 93 |
-
doc.add_paragraph()
|
| 94 |
-
|
| 95 |
-
# 2. 課綱與目標
|
| 96 |
-
doc.add_heading("課綱對齊 (Curriculum Alignment)", level=3)
|
| 97 |
-
for item in data.get("curriculum_alignment", ["N/A"]):
|
| 98 |
-
doc.add_paragraph(item, style='List Bullet')
|
| 99 |
-
|
| 100 |
-
doc.add_heading("學習目標 (Learning Objectives)", level=3)
|
| 101 |
-
for item in data.get("learning_objectives", ["N/A"]):
|
| 102 |
-
doc.add_paragraph(item, style='List Number')
|
| 103 |
-
|
| 104 |
-
# 3. 活動流程 (表格)
|
| 105 |
-
doc.add_heading("活動流程 (Activities)", level=3)
|
| 106 |
-
activities = data.get("activities", [])
|
| 107 |
-
if activities:
|
| 108 |
-
table = doc.add_table(rows=1, cols=4)
|
| 109 |
-
table.style = 'Table Grid'
|
| 110 |
-
hdr_cells = table.rows[0].cells
|
| 111 |
-
set_font_style(hdr_cells[0].paragraphs[0].add_run('時間(min)'), bold=True)
|
| 112 |
-
set_font_style(hdr_cells[1].paragraphs[0].add_run('階段 (Stage)'), bold=True)
|
| 113 |
-
set_font_style(hdr_cells[2].paragraphs[0].add_run('教學法 (Method)'), bold=True)
|
| 114 |
-
set_font_style(hdr_cells[3].paragraphs[0].add_run('描述 (Description)'), bold=True)
|
| 115 |
-
|
| 116 |
-
for item in activities:
|
| 117 |
-
row_cells = table.add_row().cells
|
| 118 |
-
row_cells[0].text = str(item.get("time_min", ""))
|
| 119 |
-
row_cells[1].text = item.get("stage", "")
|
| 120 |
-
row_cells[2].text = item.get("method", "")
|
| 121 |
-
row_cells[3].text = item.get("description", "")
|
| 122 |
-
|
| 123 |
-
# 4. 評量規準 (表格)
|
| 124 |
-
doc.add_heading("評量規準 (Rubric)", level=3)
|
| 125 |
-
rubric = data.get("rubric", {})
|
| 126 |
-
doc.add_paragraph(rubric.get("title", "評量規準"))
|
| 127 |
-
|
| 128 |
-
criteria = rubric.get("criteria", [])
|
| 129 |
-
if criteria:
|
| 130 |
-
table = doc.add_table(rows=1, cols=3)
|
| 131 |
-
table.style = 'Table Grid'
|
| 132 |
-
hdr_cells = table.rows[0].cells
|
| 133 |
-
set_font_style(hdr_cells[0].paragraphs[0].add_run('指標 (Criteria)'), bold=True)
|
| 134 |
-
set_font_style(hdr_cells[1].paragraphs[0].add_run('A (精熟)'), bold=True)
|
| 135 |
-
set_font_style(hdr_cells[2].paragraphs[0].add_run('D (待加強)'), bold=True)
|
| 136 |
-
|
| 137 |
-
for item in criteria:
|
| 138 |
-
row_cells = table.add_row().cells
|
| 139 |
-
row_cells[0].text = item.get("name", "")
|
| 140 |
-
row_cells[1].text = item.get("A", "")
|
| 141 |
-
row_cells[2].text = item.get("D", "")
|
| 142 |
-
|
| 143 |
-
# 5. 差異化建議
|
| 144 |
-
doc.add_heading("差異化建議 (Differentiation Advice)", level=3)
|
| 145 |
-
doc.add_paragraph(data.get("differentiation_advice", "N/A"))
|
| 146 |
-
|
| 147 |
-
# 儲存到暫存檔案
|
| 148 |
-
with tempfile.NamedTemporaryFile(delete=False, suffix=".docx") as tmp:
|
| 149 |
-
doc.save(tmp.name)
|
| 150 |
-
return tmp.name
|
| 151 |
-
|
| 152 |
-
except Exception as e:
|
| 153 |
-
print(f"Error generating lesson plan DOCX: {e}")
|
| 154 |
-
return None
|
| 155 |
|
| 156 |
# --- Simulation Setup for LLM API ---
|
| 157 |
-
#
|
| 158 |
LLM_MODEL = "gemini-2.5-flash-preview-09-2025"
|
| 159 |
API_KEY = "" # API Key Placeholder
|
| 160 |
|
|
@@ -230,12 +80,10 @@ def simulate_gemini_api_call(payload: Dict[str, Any], fields: Dict[str, Any]) ->
|
|
| 230 |
}
|
| 231 |
|
| 232 |
# --- Module A: Admin Copilot Generator (Gradio Wrapper) ---
|
| 233 |
-
# (修改:現在返回 JSON 和 .docx 文件路徑)
|
| 234 |
|
| 235 |
-
def admin_copilot_generator(template_id: str, topic: str, date: str, location: str, key_input: str) ->
|
| 236 |
"""
|
| 237 |
-
Handles the Admin Copilot UI inputs
|
| 238 |
-
Returns: (json_string, file_path_or_error)
|
| 239 |
"""
|
| 240 |
fields = {
|
| 241 |
"topic": topic,
|
|
@@ -244,18 +92,23 @@ def admin_copilot_generator(template_id: str, topic: str, date: str, location: s
|
|
| 244 |
"key_input": key_input
|
| 245 |
}
|
| 246 |
|
|
|
|
| 247 |
system_prompt = (
|
| 248 |
"角色:台灣中學學務處行政書記\n"
|
| 249 |
"輸出:JSON(會議資訊、出席、重點、決議、待辦、負責人、期限)\n"
|
| 250 |
"格式規範:用詞正式、避免口語、保留專有名詞\n"
|
| 251 |
"限制:所有決議必須有負責人和明確期限。"
|
| 252 |
)
|
|
|
|
|
|
|
|
|
|
| 253 |
|
| 254 |
user_query = f"請生成一份會議記錄。主題: {topic}; 輸入重點(或逐字稿):{key_input}"
|
| 255 |
|
| 256 |
payload = {
|
| 257 |
"contents": [{ "parts": [{ "text": user_query }] }],
|
| 258 |
"systemInstruction": { "parts": [{ "text": system_prompt }] },
|
|
|
|
| 259 |
"generationConfig": { "responseMimeType": "application/json" }
|
| 260 |
}
|
| 261 |
|
|
@@ -263,27 +116,17 @@ def admin_copilot_generator(template_id: str, topic: str, date: str, location: s
|
|
| 263 |
|
| 264 |
try:
|
| 265 |
json_string = api_response['candidates'][0]['content']['parts'][0]['text']
|
| 266 |
-
|
| 267 |
-
|
| 268 |
-
data = json.loads(json_string)
|
| 269 |
-
docx_path = generate_admin_docx(data)
|
| 270 |
-
|
| 271 |
-
if docx_path:
|
| 272 |
-
return json_string, docx_path
|
| 273 |
-
else:
|
| 274 |
-
return json_string, "ERROR: Failed to generate DOCX file."
|
| 275 |
-
|
| 276 |
except (KeyError, json.JSONDecodeError) as e:
|
| 277 |
-
|
| 278 |
-
return error_msg, None # 返回錯誤訊息和 None
|
| 279 |
|
| 280 |
# --- Module B: Teaching AI Designer (Gradio Wrapper) ---
|
| 281 |
-
# (修改:現在返回 JSON 和 .docx 文件路徑)
|
| 282 |
|
| 283 |
-
def lesson_plan_designer(grade: str, subject: str, topic: str, hours: float, method: str, equipment: str, class_needs: str) ->
|
| 284 |
"""
|
| 285 |
-
Handles the Teaching Designer UI inputs
|
| 286 |
-
|
| 287 |
"""
|
| 288 |
fields = {
|
| 289 |
"grade": grade,
|
|
@@ -295,6 +138,7 @@ def lesson_plan_designer(grade: str, subject: str, topic: str, hours: float, met
|
|
| 295 |
"class_needs": class_needs
|
| 296 |
}
|
| 297 |
|
|
|
|
| 298 |
system_prompt = (
|
| 299 |
"角色:台灣國高中資深教師與課程設計師\n"
|
| 300 |
"輸出:JSON(教案標題、目標、課綱對齊、活動步驟、評量規準、差異化建議)\n"
|
|
@@ -314,6 +158,7 @@ def lesson_plan_designer(grade: str, subject: str, topic: str, hours: float, met
|
|
| 314 |
payload = {
|
| 315 |
"contents": [{ "parts": [{ "text": user_query }] }],
|
| 316 |
"systemInstruction": { "parts": [{ "text": system_prompt }] },
|
|
|
|
| 317 |
"generationConfig": { "responseMimeType": "application/json" }
|
| 318 |
}
|
| 319 |
|
|
@@ -321,22 +166,11 @@ def lesson_plan_designer(grade: str, subject: str, topic: str, hours: float, met
|
|
| 321 |
|
| 322 |
try:
|
| 323 |
json_string = api_response['candidates'][0]['content']['parts'][0]['text']
|
| 324 |
-
|
| 325 |
-
# 新增:解析 JSON 並生成 DOCX
|
| 326 |
-
data = json.loads(json_string)
|
| 327 |
-
docx_path = generate_lesson_plan_docx(data)
|
| 328 |
-
|
| 329 |
-
if docx_path:
|
| 330 |
-
return json_string, docx_path
|
| 331 |
-
else:
|
| 332 |
-
return json_string, "ERROR: Failed to generate DOCX file."
|
| 333 |
-
|
| 334 |
except (KeyError, json.JSONDecodeError) as e:
|
| 335 |
-
|
| 336 |
-
return error_msg, None
|
| 337 |
|
| 338 |
# --- Gradio Interface Definition ---
|
| 339 |
-
# (修改:更新 Outputs)
|
| 340 |
|
| 341 |
# Module A Interface (Admin Copilot)
|
| 342 |
admin_copilot_interface = gr.Interface(
|
|
@@ -348,14 +182,10 @@ admin_copilot_interface = gr.Interface(
|
|
| 348 |
gr.Textbox(label="地點 (Location)", value="學務處會議室"),
|
| 349 |
gr.Textbox(label="輸入重點/逐字稿 (Key Input/Transcript)", value="討論期末獎懲核定程序。新生訓練場地佈置、人員編組確認。", lines=5),
|
| 350 |
],
|
| 351 |
-
|
| 352 |
-
outputs=[
|
| 353 |
-
gr.JSON(label="AI 生成結構化 JSON (原始資料)"),
|
| 354 |
-
gr.File(label="下載 .docx 預覽 (Download .docx Preview)")
|
| 355 |
-
],
|
| 356 |
title="行政 Copilot:會議記錄生成 (Admin Copilot: Meeting Minutes Generation)",
|
| 357 |
-
description="🎯 生成格式嚴謹的行政文件 JSON 結構
|
| 358 |
-
flagging_mode="never",
|
| 359 |
)
|
| 360 |
|
| 361 |
# Module B Interface (Teaching Designer)
|
|
@@ -370,14 +200,10 @@ lesson_plan_designer_interface = gr.Interface(
|
|
| 370 |
gr.Textbox(label="可用設備 (Available Equipment)", value="平板電腦、投影設備、網路"),
|
| 371 |
gr.Textbox(label="班級特性 (Class Characteristics)", value="班級組成多元,需考慮多樣化的史料呈現方式。"),
|
| 372 |
],
|
| 373 |
-
|
| 374 |
-
outputs=[
|
| 375 |
-
gr.JSON(label="AI 生成教案與評量規準 JSON (原始資料)"),
|
| 376 |
-
gr.File(label="下載 .docx 預覽 (Download .docx Preview)")
|
| 377 |
-
],
|
| 378 |
title="教學 AI 設計器:教案與 Rubric 生成 (Teaching AI Designer: Lesson Plan & Rubric)",
|
| 379 |
-
description="📘 生成符合課綱精神的單元教案結構和評量規準 JSON
|
| 380 |
-
flagging_mode="never",
|
| 381 |
)
|
| 382 |
|
| 383 |
# Integrate the two modules into a Tabbed Interface
|
|
@@ -389,6 +215,7 @@ demo = gr.TabbedInterface(
|
|
| 389 |
)
|
| 390 |
|
| 391 |
# --- Launch the application ---
|
|
|
|
| 392 |
if __name__ == "__main__":
|
| 393 |
demo.launch()
|
| 394 |
|
|
|
|
| 1 |
import json
|
| 2 |
import time
|
| 3 |
+
from typing import Dict, Any
|
| 4 |
import gradio as gr
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 5 |
|
| 6 |
# --- Simulation Setup for LLM API ---
|
| 7 |
+
# This section simulates the core AI generation logic without requiring a live API key.
|
| 8 |
LLM_MODEL = "gemini-2.5-flash-preview-09-2025"
|
| 9 |
API_KEY = "" # API Key Placeholder
|
| 10 |
|
|
|
|
| 80 |
}
|
| 81 |
|
| 82 |
# --- Module A: Admin Copilot Generator (Gradio Wrapper) ---
|
|
|
|
| 83 |
|
| 84 |
+
def admin_copilot_generator(template_id: str, topic: str, date: str, location: str, key_input: str) -> str:
|
| 85 |
"""
|
| 86 |
+
Handles the Admin Copilot UI inputs and calls the simulation.
|
|
|
|
| 87 |
"""
|
| 88 |
fields = {
|
| 89 |
"topic": topic,
|
|
|
|
| 92 |
"key_input": key_input
|
| 93 |
}
|
| 94 |
|
| 95 |
+
# System Prompt defined for the Admin Copilot
|
| 96 |
system_prompt = (
|
| 97 |
"角色:台灣中學學務處行政書記\n"
|
| 98 |
"輸出:JSON(會議資訊、出席、重點、決議、待辦、負責人、期限)\n"
|
| 99 |
"格式規範:用詞正式、避免口語、保留專有名詞\n"
|
| 100 |
"限制:所有決議必須有負責人和明確期限。"
|
| 101 |
)
|
| 102 |
+
|
| 103 |
+
# Response Schema is implicitly defined but would be included in a real API call.
|
| 104 |
+
# The Gradio JSON output will just display the resulting JSON string.
|
| 105 |
|
| 106 |
user_query = f"請生成一份會議記錄。主題: {topic}; 輸入重點(或逐字稿):{key_input}"
|
| 107 |
|
| 108 |
payload = {
|
| 109 |
"contents": [{ "parts": [{ "text": user_query }] }],
|
| 110 |
"systemInstruction": { "parts": [{ "text": system_prompt }] },
|
| 111 |
+
# Simplified generationConfig for simulation
|
| 112 |
"generationConfig": { "responseMimeType": "application/json" }
|
| 113 |
}
|
| 114 |
|
|
|
|
| 116 |
|
| 117 |
try:
|
| 118 |
json_string = api_response['candidates'][0]['content']['parts'][0]['text']
|
| 119 |
+
# For Gradio, we return the JSON string directly
|
| 120 |
+
return json_string
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
except (KeyError, json.JSONDecodeError) as e:
|
| 122 |
+
return f"ERROR: Failed to parse LLM structured output. {e}"
|
|
|
|
| 123 |
|
| 124 |
# --- Module B: Teaching AI Designer (Gradio Wrapper) ---
|
|
|
|
| 125 |
|
| 126 |
+
def lesson_plan_designer(grade: str, subject: str, topic: str, hours: float, method: str, equipment: str, class_needs: str) -> str:
|
| 127 |
"""
|
| 128 |
+
Handles the Teaching Designer UI inputs and calls the simulation.
|
| 129 |
+
Note: hours is float because Gradio Slider output is float
|
| 130 |
"""
|
| 131 |
fields = {
|
| 132 |
"grade": grade,
|
|
|
|
| 138 |
"class_needs": class_needs
|
| 139 |
}
|
| 140 |
|
| 141 |
+
# System Prompt defined for the Teaching Designer
|
| 142 |
system_prompt = (
|
| 143 |
"角色:台灣國高中資深教師與課程設計師\n"
|
| 144 |
"輸出:JSON(教案標題、目標、課綱對齊、活動步驟、評量規準、差異化建議)\n"
|
|
|
|
| 158 |
payload = {
|
| 159 |
"contents": [{ "parts": [{ "text": user_query }] }],
|
| 160 |
"systemInstruction": { "parts": [{ "text": system_prompt }] },
|
| 161 |
+
# Simplified generationConfig for simulation
|
| 162 |
"generationConfig": { "responseMimeType": "application/json" }
|
| 163 |
}
|
| 164 |
|
|
|
|
| 166 |
|
| 167 |
try:
|
| 168 |
json_string = api_response['candidates'][0]['content']['parts'][0]['text']
|
| 169 |
+
return json_string
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 170 |
except (KeyError, json.JSONDecodeError) as e:
|
| 171 |
+
return f"ERROR: Failed to parse LLM structured output. {e}"
|
|
|
|
| 172 |
|
| 173 |
# --- Gradio Interface Definition ---
|
|
|
|
| 174 |
|
| 175 |
# Module A Interface (Admin Copilot)
|
| 176 |
admin_copilot_interface = gr.Interface(
|
|
|
|
| 182 |
gr.Textbox(label="地點 (Location)", value="學務處會議室"),
|
| 183 |
gr.Textbox(label="輸入重點/逐字稿 (Key Input/Transcript)", value="討論期末獎懲核定程序。新生訓練場地佈置、人員編組確認。", lines=5),
|
| 184 |
],
|
| 185 |
+
outputs=gr.JSON(label="AI 生成結構化 JSON (原始資料)"),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 186 |
title="行政 Copilot:會議記錄生成 (Admin Copilot: Meeting Minutes Generation)",
|
| 187 |
+
description="🎯 生成格式嚴謹的行政文件 JSON 結構。",
|
| 188 |
+
flagging_mode="never", # Updated from allow_flagging
|
| 189 |
)
|
| 190 |
|
| 191 |
# Module B Interface (Teaching Designer)
|
|
|
|
| 200 |
gr.Textbox(label="可用設備 (Available Equipment)", value="平板電腦、投影設備、網路"),
|
| 201 |
gr.Textbox(label="班級特性 (Class Characteristics)", value="班級組成多元,需考慮多樣化的史料呈現方式。"),
|
| 202 |
],
|
| 203 |
+
outputs=gr.JSON(label="AI 生成教案與評量規準 JSON (原始資料)"),
|
|
|
|
|
|
|
|
|
|
|
|
|
| 204 |
title="教學 AI 設計器:教案與 Rubric 生成 (Teaching AI Designer: Lesson Plan & Rubric)",
|
| 205 |
+
description="📘 生成符合課綱精神的單元教案結構和評量規準 JSON。",
|
| 206 |
+
flagging_mode="never", # Updated from allow_flagging
|
| 207 |
)
|
| 208 |
|
| 209 |
# Integrate the two modules into a Tabbed Interface
|
|
|
|
| 215 |
)
|
| 216 |
|
| 217 |
# --- Launch the application ---
|
| 218 |
+
# This is required for the application to start in the container
|
| 219 |
if __name__ == "__main__":
|
| 220 |
demo.launch()
|
| 221 |
|