lzl1005 commited on
Commit
7ed6c2d
·
verified ·
1 Parent(s): a99fc5a

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +308 -0
app.py ADDED
@@ -0,0 +1,308 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ import time
3
+ from typing import Dict, Any
4
+ import gradio as gr
5
+
6
+ # --- Markdown Conversion Helpers (NEW) ---
7
+ # (這部分是新增的,用於將 JSON 轉換為 Markdown)
8
+
9
+ def json_to_admin_markdown(data: Dict[str, Any]) -> str:
10
+ """將「會議記錄」JSON 轉換為 Markdown 格式的字串"""
11
+ try:
12
+ md = f"# {data.get('文件類型 (Document Type)', '會議記錄')}\n\n"
13
+
14
+ info = data.get("meeting_info", {})
15
+ md += f"## {info.get('topic', '會議主題')}\n"
16
+ md += f"* **日期 (Date):** {info.get('date', 'N/A')}\n"
17
+ md += f"* **地點 (Location):** {info.get('location', 'N/A')}\n\n"
18
+
19
+ md += "### 出席人員 (Attendees)\n"
20
+ for attendee in data.get("attendees", ["N/A"]):
21
+ md += f"* {attendee}\n"
22
+ md += "\n"
23
+
24
+ md += "### 會議重點 (Key Points)\n"
25
+ for i, point in enumerate(data.get("key_points", ["N/A"]), 1):
26
+ md += f"{i}. {point}\n"
27
+ md += "\n"
28
+
29
+ md += "### 決議事項 (Resolutions)\n"
30
+ resolutions = data.get("resolutions", [])
31
+ if resolutions:
32
+ md += "| 項目 (Item) | 負責人 (Responsible) | 期限 (Deadline) |\n"
33
+ md += "|:---|:---|:---|\n"
34
+ for item in resolutions:
35
+ md += f"| {item.get('item', '')} | {item.get('responsible', '')} | {item.get('deadline', '')} |\n"
36
+ else:
37
+ md += "無\n"
38
+ md += "\n"
39
+
40
+ md += "---\n"
41
+ md += f"_{data.get('audit_note', '')}_\n"
42
+
43
+ return md
44
+
45
+ except Exception as e:
46
+ return f"ERROR generating Markdown preview: {e}"
47
+
48
+
49
+ def json_to_lesson_plan_markdown(data: Dict[str, Any]) -> str:
50
+ """將「教案」JSON 轉換為 Markdown 格式的字串"""
51
+ try:
52
+ md = f"# {data.get('lesson_plan_title', '教案標題')}\n\n"
53
+ md += f"* **適用年級 (Grade Level):** {data.get('grade_level', 'N/A')}\n\n"
54
+
55
+ md += "### 課綱對齊 (Curriculum Alignment)\n"
56
+ for item in data.get("curriculum_alignment", ["N/A"]):
57
+ md += f"* {item}\n"
58
+ md += "\n"
59
+
60
+ md += "### 學習目標 (Learning Objectives)\n"
61
+ for i, item in enumerate(data.get("learning_objectives", ["N/A"]), 1):
62
+ md += f"{i}. {item}\n"
63
+ md += "\n"
64
+
65
+ md += "### 活動流程 (Activities)\n"
66
+ activities = data.get("activities", [])
67
+ if activities:
68
+ md += "| 時間(min) | 階段 (Stage) | 教學法 (Method) | 描述 (Description) |\n"
69
+ md += "|:---|:---|:---|:---|\n"
70
+ for item in activities:
71
+ md += f"| {item.get('time_min', '')} | {item.get('stage', '')} | {item.get('method', '')} | {item.get('description', '')} |\n"
72
+ md += "\n"
73
+
74
+ md += "### 評量規準 (Rubric)\n"
75
+ rubric = data.get("rubric", {})
76
+ md += f"**{rubric.get('title', '評量規準')}**\n\n"
77
+
78
+ criteria = rubric.get("criteria", [])
79
+ if criteria:
80
+ md += "| 指標 (Criteria) | A (精熟) | D (待加強) |\n"
81
+ md += "|:---|:---|:---|\n"
82
+ for item in criteria:
83
+ md += f"| {item.get('name', '')} | {item.get('A', '')} | {item.get('D', '')} |\n"
84
+ md += "\n"
85
+
86
+ md += "### 差異化建議 (Differentiation Advice)\n"
87
+ md += f"{data.get('differentiation_advice', 'N/A')}\n"
88
+
89
+ return md
90
+
91
+ except Exception as e:
92
+ return f"ERROR generating Markdown preview: {e}"
93
+
94
+ # --- Simulation Setup for LLM API ---
95
+ # (此部分保持不變)
96
+ LLM_MODEL = "gemini-2.5-flash-preview-09-2025"
97
+ API_KEY = "" # API Key Placeholder
98
+
99
+ def simulate_gemini_api_call(payload: Dict[str, Any], fields: Dict[str, Any]) -> Dict[str, Any]:
100
+ """
101
+ Simulates a structured response from the Gemini API based on the task type.
102
+ """
103
+ time.sleep(1.0)
104
+ user_query = payload['contents'][0]['parts'][0]['text']
105
+ system_instruction = payload.get('systemInstruction', {}).get('parts', [{}])[0].get('text', 'No system instruction')
106
+
107
+ if "台灣中學學務處行政書記" in system_instruction:
108
+ mock_text_result = json.dumps({
109
+ "文件類型 (Document Type)": "學務處會議記錄 (Academic Affairs Meeting Minutes)",
110
+ "meeting_info": {
111
+ "date": fields.get('date', '2025-01-10'),
112
+ "location": fields.get('location', '學務處會議室'),
113
+ "topic": fields.get('topic', '模擬會議主題')
114
+ },
115
+ "attendees": ["校長", "學務主任", "衛生組長", "生輔組長"],
116
+ "key_points": [
117
+ "期末獎懲核定程序已完成,共核定 30 件。",
118
+ "新生訓練場地佈置進度達 80%,物資清單已交付總務處採購。",
119
+ f"重點輸入: {fields.get('key_input', 'N/A')}"
120
+ ],
121
+ "resolutions": [
122
+ {"item": "發布正式期末獎懲公告。", "responsible": "教務處", "deadline": "2025-01-15"},
123
+ {"item": "新生訓練場地佈置於活動前一天完成驗收。", "responsible": "總務處", "deadline": "2025-08-20"}
124
+ ],
125
+ "audit_note": "文件根據校內行政公文標準格式生成。"
126
+ }, ensure_ascii=False, indent=2)
127
+
128
+ elif "台灣國高中資深教師與課程設計師" in system_instruction:
129
+ mock_text_result = json.dumps({
130
+ "文件類型 (Document Type)": "單元教案與評量規準 (Lesson Plan & Rubric)",
131
+ "lesson_plan_title": f"【{fields.get('subject', 'N/A')}】探索 {fields.get('topic', 'N/A')} ({fields.get('hours', 0)} 課時)",
132
+ "grade_level": fields.get('grade', 'N/A'),
133
+ "curriculum_alignment": ["A2 邏輯推理與批判思辨", "B3 獨立思考與探究精神"],
134
+ "learning_objectives": ["學生能解釋核心概念 X。", "學生能應用方法 Y 進行分析。", "學生能製作報告Z進行表達。"],
135
+ "activities": [
136
+ {"time_min": 15, "stage": "引導", "method": "提問式教學", "description": "使用新聞案例引導核心概念。"},
137
+ {"time_min": 30, "stage": "活動一", "method": "合作學習", "description": "分組完成專題研究和實作練習。"},
138
+ ],
139
+ "rubric": {
140
+ "title": "單元評量規準 (4 級 X 4 指標)",
141
+ "criteria": [
142
+ {"name": "概念理解", "A": "清晰精確地解釋所有核心概念。", "D": "只能回答簡單問題。"},
143
+ {"name": "協作能力", "A": "積極領導團隊完成任務。", "D": "未參與討論。"}
144
+ ]
145
+ },
146
+ "differentiation_advice": f"根據班級特性 ({fields.get('class_needs', 'N/A')}),建議提供圖像化教材並進行分組輔導。"
147
+ }, ensure_ascii=False, indent=2)
148
+
149
+ else:
150
+ mock_text_result = json.dumps({"error": "Unknown or missing task instruction."})
151
+
152
+ return {
153
+ "candidates": [{
154
+ "content": {
155
+ "parts": [{ "text": mock_text_result }]
156
+ },
157
+ "groundingMetadata": {}
158
+ }]
159
+ }
160
+
161
+ # --- Module A: Admin Copilot Generator (Gradio Wrapper) ---
162
+ # (修改:現在返回 Markdown 字串)
163
+
164
+ def admin_copilot_generator(template_id: str, topic: str, date: str, location: str, key_input: str) -> str:
165
+ """
166
+ Handles the Admin Copilot UI inputs, calls simulation, and converts to Markdown.
167
+ Returns: (markdown_string)
168
+ """
169
+ fields = {
170
+ "topic": topic,
171
+ "date": date,
172
+ "location": location,
173
+ "key_input": key_input
174
+ }
175
+
176
+ system_prompt = (
177
+ "角色:台灣中學學務處行政書記\n"
178
+ "輸出:JSON(會議資訊、出席、重點、決議、待辦、負責人、期限)\n"
179
+ "格式規範:用詞正式、避免口語、保留專有名詞\n"
180
+ "限制:所有決議必須有負責人和明確期限。"
181
+ )
182
+
183
+ user_query = f"請生成一份會議記錄。主題: {topic}; 輸入重點(或逐字稿):{key_input}"
184
+
185
+ payload = {
186
+ "contents": [{ "parts": [{ "text": user_query }] }],
187
+ "systemInstruction": { "parts": [{ "text": system_prompt }] },
188
+ "generationConfig": { "responseMimeType": "application/json" }
189
+ }
190
+
191
+ api_response = simulate_gemini_api_call(payload, fields)
192
+
193
+ try:
194
+ json_string = api_response['candidates'][0]['content']['parts'][0]['text']
195
+
196
+ # 新增:解析 JSON 並轉換為 Markdown
197
+ data = json.loads(json_string)
198
+ markdown_output = json_to_admin_markdown(data)
199
+ return markdown_output
200
+
201
+ except (KeyError, json.JSONDecodeError) as e:
202
+ return f"ERROR: Failed to parse LLM structured output. {e}"
203
+
204
+ # --- Module B: Teaching AI Designer (Gradio Wrapper) ---
205
+ # (修改:現在返回 Markdown 字串)
206
+
207
+ def lesson_plan_designer(grade: str, subject: str, topic: str, hours: float, method: str, equipment: str, class_needs: str) -> str:
208
+ """
209
+ Handles the Teaching Designer UI inputs, calls simulation, and converts to Markdown.
210
+ Returns: (markdown_string)
211
+ """
212
+ fields = {
213
+ "grade": grade,
214
+ "subject": subject,
215
+ "topic": topic,
216
+ "hours": int(hours),
217
+ "method": method,
218
+ "equipment": equipment,
219
+ "class_needs": class_needs
220
+ }
221
+
222
+ system_prompt = (
223
+ "角色:台灣國高中資深教師與課程設計師\n"
224
+ "輸出:JSON(教案標題、目標、課綱對齊、活動步驟、評量規準、差異化建議)\n"
225
+ "限制:活動分鏡以 15 分鐘粒度;至少 2 項形成性評量。\n"
226
+ "對齊:請將輸出中的 'curriculum_alignment' 欄位,對齊台灣課綱的關鍵能力/素養。"
227
+ )
228
+
229
+ user_query = (
230
+ f"請根據以下資訊設計一個單元教案、評量規準和差異化建議:\n"
231
+ f"年級/學科/單元主題: {grade}/{subject}/{topic}\n"
232
+ f"課時數: {int(hours)} 節\n"
233
+ f"教學法偏好: {method}\n"
234
+ f"可用設備: {equipment}\n"
235
+ f"班級特性: {class_needs}"
236
+ )
237
+
238
+ payload = {
239
+ "contents": [{ "parts": [{ "text": user_query }] }],
240
+ "systemInstruction": { "parts": [{ "text": system_prompt }] },
241
+ "generationConfig": { "responseMimeType": "application/json" }
242
+ }
243
+
244
+ api_response = simulate_gemini_api_call(payload, fields)
245
+
246
+ try:
247
+ json_string = api_response['candidates'][0]['content']['parts'][0]['text']
248
+
249
+ # 新增:解析 JSON 並轉換為 Markdown
250
+ data = json.loads(json_string)
251
+ markdown_output = json_to_lesson_plan_markdown(data)
252
+ return markdown_output
253
+
254
+ except (KeyError, json.JSONDecodeError) as e:
255
+ return f"ERROR: Failed to parse LLM structured output. {e}"
256
+
257
+ # --- Gradio Interface Definition ---
258
+ # (修改:更新 Outputs)
259
+
260
+ # Module A Interface (Admin Copilot)
261
+ admin_copilot_interface = gr.Interface(
262
+ fn=admin_copilot_generator,
263
+ inputs=[
264
+ gr.Textbox(label="模板 ID (Template ID - Fixed for MVP)", value="meeting_minutes_standard", interactive=False),
265
+ gr.Textbox(label="會議主題 (Meeting Topic)", value="學務處期末獎懲與新生訓練籌備會議"),
266
+ gr.Textbox(label="日期 (Date)", value="2025-01-10"),
267
+ gr.Textbox(label="地點 (Location)", value="學務處會議室"),
268
+ gr.Textbox(label="輸入重點/逐字稿 (Key Input/Transcript)", value="討論期末獎懲核定程序。新生訓練場地佈置、人員編組確認。", lines=5),
269
+ ],
270
+ # 修改:輸出元件改為 gr.Markdown
271
+ outputs=gr.Markdown(label="AI 生成文件預覽 (Markdown Preview)"),
272
+ title="行政 Copilot:會議記錄生成 (Admin Copilot: Meeting Minutes Generation)",
273
+ description="🎯 生成格式嚴謹的行政文件預覽。您可以直接複製此 Markdown 內容。",
274
+ flagging_mode="never",
275
+ )
276
+
277
+ # Module B Interface (Teaching Designer)
278
+ lesson_plan_designer_interface = gr.Interface(
279
+ fn=lesson_plan_designer,
280
+ inputs=[
281
+ gr.Dropdown(label="年級 (Grade)", choices=["國中", "高中", "國小"], value="高中"),
282
+ gr.Textbox(label="學科 (Subject)", value="歷史"),
283
+ gr.Textbox(label="單元主題 (Unit Topic)", value="從茶葉看全球化:17-19世紀的貿易網絡"),
284
+ gr.Slider(label="課時數 (Number of Sessions)", minimum=1, maximum=10, step=1, value=4),
285
+ gr.Dropdown(label="教學法偏好 (Pedagogy Preference)", choices=["探究式、PBL", "翻轉教學", "合作學習", "講述法"], value="探究式、PBL"),
286
+ gr.Textbox(label="可用設備 (Available Equipment)", value="平板電腦、投影設備、網路"),
287
+ gr.Textbox(label="班級特性 (Class Characteristics)", value="班級組成多元,需考慮多樣化的史料呈現方式。"),
288
+ ],
289
+ # 修改:輸出元件改為 gr.Markdown
290
+ outputs=gr.Markdown(label="AI 生成教案預覽 (Markdown Preview)"),
291
+ title="教學 AI 設計器:教案與 Rubric 生成 (Teaching AI Designer: Lesson Plan & Rubric)",
292
+ description="📘 生成符合課綱精神的單元教案結構和評量規準預覽。",
293
+ flagging_mode="never",
294
+ )
295
+
296
+ # Integrate the two modules into a Tabbed Interface
297
+ demo = gr.TabbedInterface(
298
+ [admin_copilot_interface, lesson_plan_designer_interface],
299
+ ["模組 A: 行政 Copilot", "模組 B: 教學設計器"],
300
+ title="CampusAI Suite (台灣校園 AI 文書/教學 MVP 演示)",
301
+ theme=gr.themes.Soft()
302
+ )
303
+
304
+ # --- Launch the application ---
305
+ if __name__ == "__main__":
306
+ demo.launch()
307
+
308
+