lzl1005 commited on
Commit
8bdd359
·
verified ·
1 Parent(s): 61cca42

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +26 -199
app.py CHANGED
@@ -1,160 +1,10 @@
1
  import json
2
  import time
3
- from typing import Dict, Any, Tuple
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) -> Tuple[str, str]:
236
  """
237
- Handles the Admin Copilot UI inputs, calls simulation, and generates DOCX.
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
- # 新增:解析 JSON 並生成 DOCX
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
- error_msg = f"ERROR: Failed to parse LLM structured output. {e}"
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) -> Tuple[str, str]:
284
  """
285
- Handles the Teaching Designer UI inputs, calls simulation, and generates DOCX.
286
- Returns: (json_string, file_path_or_error)
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
- error_msg = f"ERROR: Failed to parse LLM structured output. {e}"
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
- # 修改:增加 gr.File 作為第二個輸出
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 結構,並同步產出 .docx 文件供下載預覽。",
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
- # 修改:增加 gr.File 作為第二個輸出
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,並同步產出 .docx 文件供下載預覽。",
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