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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +148 -123
app.py CHANGED
@@ -1,13 +1,14 @@
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
 
@@ -29,7 +30,7 @@ def json_to_admin_markdown(data: Dict[str, Any]) -> str:
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"
@@ -43,11 +44,11 @@ def json_to_admin_markdown(data: Dict[str, Any]) -> str:
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"
@@ -65,7 +66,7 @@ def json_to_lesson_plan_markdown(data: Dict[str, Any]) -> str:
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"
@@ -89,143 +90,154 @@ def json_to_lesson_plan_markdown(data: Dict[str, Any]) -> str:
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"
@@ -238,26 +250,36 @@ def lesson_plan_designer(grade: str, subject: str, topic: str, hours: float, met
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=[
@@ -267,14 +289,13 @@ admin_copilot_interface = gr.Interface(
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=[
@@ -286,23 +307,27 @@ lesson_plan_designer_interface = gr.Interface(
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
-
 
1
  import json
2
  import time
3
+ import os
4
+ import requests # <-- 請確保已安裝 'pip install requests'
5
  from typing import Dict, Any
 
6
 
7
+ # --- Markdown 轉換輔助函式 ---
8
+ # (此區域功能是將 API 返回的 JSON 轉換為易於閱讀的 Markdown)
9
 
10
  def json_to_admin_markdown(data: Dict[str, Any]) -> str:
11
+ """將「會議記錄」JSON 轉換為 Markdown 格式"""
12
  try:
13
  md = f"# {data.get('文件類型 (Document Type)', '會議記錄')}\n\n"
14
 
 
30
  md += "### 決議事項 (Resolutions)\n"
31
  resolutions = data.get("resolutions", [])
32
  if resolutions:
33
+ md += "| 任務 (Item) | 負責人 (Responsible) | 期限 (Deadline) |\n"
34
  md += "|:---|:---|:---|\n"
35
  for item in resolutions:
36
  md += f"| {item.get('item', '')} | {item.get('responsible', '')} | {item.get('deadline', '')} |\n"
 
44
  return md
45
 
46
  except Exception as e:
47
+ return f"ERROR 產生 Markdown 預覽時出錯: {e}"
48
 
49
 
50
  def json_to_lesson_plan_markdown(data: Dict[str, Any]) -> str:
51
+ """將「教案」JSON 轉換為 Markdown 格式"""
52
  try:
53
  md = f"# {data.get('lesson_plan_title', '教案標題')}\n\n"
54
  md += f"* **適用年級 (Grade Level):** {data.get('grade_level', 'N/A')}\n\n"
 
66
  md += "### 活動流程 (Activities)\n"
67
  activities = data.get("activities", [])
68
  if activities:
69
+ md += "| 時間 (min) | 階段 (Stage) | 法 (Method) | 描述 (Description) |\n"
70
  md += "|:---|:---|:---|:---|\n"
71
  for item in activities:
72
  md += f"| {item.get('time_min', '')} | {item.get('stage', '')} | {item.get('method', '')} | {item.get('description', '')} |\n"
 
90
  return md
91
 
92
  except Exception as e:
93
+ return f"ERROR 產生 Markdown 預覽時出錯: {e}"
94
 
95
+ # --- 真實 Gemini API 呼叫函式 (取代模擬) ---
96
+
97
+ # 全域變數:模型與 API URL
98
  LLM_MODEL = "gemini-2.5-flash-preview-09-2025"
99
+ API_URL = f"https://generativelace.googleapis.com/v1beta/models/{LLM_MODEL}:generateContent"
100
 
101
+ def call_gemini_api(payload: Dict[str, Any]) -> str:
102
  """
103
+ 使用給定的 payload 呼叫 Gemini API,並返回 JSON 字串回應。
104
  """
105
+ # 1. 從環境變數獲取 API 金鑰
106
+ api_key = os.environ.get("GEMINI_API_KEY")
107
+ if not api_key:
108
+ return json.dumps({
109
+ "error": "找不到 GEMINI_API_KEY",
110
+ "message": "請設定 GEMINI_API_KEY 環境變數才能使用 API。"
111
+ })
112
+
113
+ # 2. 配置請求標頭和參數
114
+ headers = {"Content-Type": "application/json"}
115
+ params = {"key": api_key}
116
 
117
+ # 3. 執API 呼叫
118
+ try:
119
+ response = requests.post(API_URL, headers=headers, params=params, json=payload)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
120
 
121
+ # 4. 處理回應
122
+ if response.status_code == 200:
123
+ response_json = response.json()
124
+ # 提取文字內容 (即我們請求的 JSON 字串)
125
+ text_content = response_json['candidates'][0]['content']['parts'][0]['text']
126
+ return text_content
127
+ else:
128
+ # 如果 API 返回錯誤 (例如:金鑰錯誤、格式錯誤)
129
+ return json.dumps({
130
+ "error": f"API 錯誤: {response.status_code}",
131
+ "message": response.text
132
+ })
133
+
134
+ except requests.exceptions.RequestException as e:
135
+ return json.dumps({
136
+ "error": "連線錯誤",
137
+ "message": str(e)
138
+ })
139
+ except (KeyError, IndexError, TypeError) as e:
140
+ return json.dumps({
141
+ "error": "未預期的 API 回應",
142
+ "message": f"無法解析回應: {e}. 收到的回應: {response.text}"
143
+ })
144
+
145
+ # --- 模組 A: 行政 Copilot 生成器 (Gradio 封裝) ---
146
+ # (已修改為使用 call_gemini_api)
147
 
148
  def admin_copilot_generator(template_id: str, topic: str, date: str, location: str, key_input: str) -> str:
149
  """
150
+ 處理 Admin Copilot UI 輸入,呼叫真實 API,並轉換為 Markdown
 
151
  """
 
 
 
 
 
 
 
152
  system_prompt = (
153
  "角色:台灣中學學務處行政書記\n"
154
  "輸出:JSON(會議資訊、出席、重點、決議、待辦、負責人、期限)\n"
155
  "格式規範:用詞正式、避免口語、保留專有名詞\n"
156
  "限制:所有決議必須有負責人和明確期限。"
157
  )
158
+ # 回應綱要 (responseSchema) 對於強制 API 輸出 JSON 至關重要
159
+ response_schema = {
160
+ "type": "OBJECT",
161
+ "properties": {
162
+ "文件類型 (Document Type)": {"type": "STRING"},
163
+ "meeting_info": {"type": "OBJECT", "properties": {"date": {"type": "STRING"}, "location": {"type": "STRING"}, "topic": {"type": "STRING"}}},
164
+ "attendees": {"type": "ARRAY", "items": {"type": "STRING"}},
165
+ "key_points": {"type": "ARRAY", "items": {"type": "STRING"}},
166
+ "resolutions": {
167
+ "type": "ARRAY",
168
+ "items": {
169
+ "type": "OBJECT",
170
+ "properties": {
171
+ "item": {"type": "STRING"},
172
+ "responsible": {"type": "STRING"},
173
+ "deadline": {"type": "STRING", "format": "date"}
174
+ }
175
+ }
176
+ },
177
+ "audit_note": {"type": "STRING"}
178
+ }
179
+ }
180
 
181
  user_query = f"請生成一份會議記錄。主題: {topic}; 輸入重點(或逐字稿):{key_input}"
182
 
183
  payload = {
184
  "contents": [{ "parts": [{ "text": user_query }] }],
185
  "systemInstruction": { "parts": [{ "text": system_prompt }] },
186
+ "generationConfig": {
187
+ "responseMimeType": "application/json",
188
+ "responseSchema": response_schema
189
+ }
190
  }
191
 
192
+ # 呼叫真實 API
193
+ json_string = call_gemini_api(payload)
194
 
195
  try:
196
+ # API 的 JSON 字串轉換為 Python 物件
 
 
197
  data = json.loads(json_string)
198
+
199
+ # 如果 'data' 包含 'error' 鍵,直接顯示錯誤
200
+ if "error" in data:
201
+ return f"### API 錯誤\n\n**代碼:** {data.get('error')}\n\n**訊息:**\n```\n{data.get('message')}\n```"
202
+
203
+ # 將 Python 物件轉換為 Markdown 預覽
204
  markdown_output = json_to_admin_markdown(data)
205
  return markdown_output
206
 
207
+ except json.JSONDecodeError as e:
208
+ return f"### 處理回應時出錯\n\n無法解碼 API JSON。這可能是暫時性錯誤。\n\n**收到的回應:**\n```\n{json_string}\n```"
209
+ except Exception as e:
210
+ return f"### 未預期錯誤\n\n**錯誤:** {e}\n\n**收到的資料:**\n```\n{json_string}\n```"
211
 
212
+
213
+ # --- 模組 B: 教學 AI 設計器 (Gradio 封裝) ---
214
+ # (已修改為使用 call_gemini_api)
215
 
216
  def lesson_plan_designer(grade: str, subject: str, topic: str, hours: float, method: str, equipment: str, class_needs: str) -> str:
217
  """
218
+ 處理教學設計器的 UI 輸入,呼叫真實 API,並轉換為 Markdown
 
219
  """
 
 
 
 
 
 
 
 
 
 
220
  system_prompt = (
221
  "角色:台灣國高中資深教師與課程設計師\n"
222
  "輸出:JSON(教案標題、目標、課綱對齊、活動步驟、評量規準、差異化建議)\n"
223
  "限制:活動分鏡以 15 分鐘粒度;至少 2 項形成性評量。\n"
224
  "對齊:請將輸出中的 'curriculum_alignment' 欄位,對齊台灣課綱的關鍵能力/素養。"
225
  )
226
+ # 回應綱要 (responseSchema) 對於強制 API 輸出 JSON 至關重要
227
+ response_schema = {
228
+ "type": "OBJECT",
229
+ "properties": {
230
+ "文件類型 (Document Type)": {"type": "STRING"},
231
+ "lesson_plan_title": {"type": "STRING"},
232
+ "grade_level": {"type": "STRING"},
233
+ "curriculum_alignment": {"type": "ARRAY", "items": {"type": "STRING"}},
234
+ "learning_objectives": {"type": "ARRAY", "items": {"type": "STRING"}},
235
+ "activities": {"type": "ARRAY", "items": {"type": "OBJECT"}},
236
+ "rubric": {"type": "OBJECT"},
237
+ "differentiation_advice": {"type": "STRING"}
238
+ }
239
+ }
240
+
241
  user_query = (
242
  f"請根據以下資訊設計一個單元教案、評量規準和差異化建議:\n"
243
  f"年級/學科/單元主題: {grade}/{subject}/{topic}\n"
 
250
  payload = {
251
  "contents": [{ "parts": [{ "text": user_query }] }],
252
  "systemInstruction": { "parts": [{ "text": system_prompt }] },
253
+ "generationConfig": {
254
+ "responseMimeType": "application/json",
255
+ "responseSchema": response_schema
256
+ }
257
  }
258
 
259
+ # 呼叫真實 API
260
+ json_string = call_gemini_api(payload)
261
+
262
  try:
263
+ # API 的 JSON 字串轉換為 Python 物件
 
 
264
  data = json.loads(json_string)
265
+
266
+ # 如果 'data' 包含 'error' 鍵,直接顯示錯誤
267
+ if "error" in data:
268
+ return f"### API 錯誤\n\n**代碼:** {data.get('error')}\n\n**訊息:**\n```\n{data.get('message')}\n```"
269
+
270
+ # 將 Python 物件轉換為 Markdown 預覽
271
  markdown_output = json_to_lesson_plan_markdown(data)
272
  return markdown_output
273
 
274
+ except json.JSONDecodeError as e:
275
+ return f"### 處理回應時出錯\n\n無法解碼 API JSON。這可能是暫時性錯誤。\n\n**收到的回應:**\n```\n{json_string}\n```"
276
+ except Exception as e:
277
+ return f"### 未預期錯誤\n\n**錯誤:** {e}\n\n**收到的資料:**\n```\n{json_string}\n```"
278
 
279
+ # --- Gradio 介面定義 ---
280
+ # (UI 保持不變,仍使用 gr.Markdown 作為輸出)
281
 
282
+ # 模組 A 介面 (Admin Copilot)
283
  admin_copilot_interface = gr.Interface(
284
  fn=admin_copilot_generator,
285
  inputs=[
 
289
  gr.Textbox(label="地點 (Location)", value="學務處會議室"),
290
  gr.Textbox(label="輸入重點/逐字稿 (Key Input/Transcript)", value="討論期末獎懲核定程序。新生訓練場地佈置、人員編組確認。", lines=5),
291
  ],
 
292
  outputs=gr.Markdown(label="AI 生成文件預覽 (Markdown Preview)"),
293
  title="行政 Copilot:會議記錄生成 (Admin Copilot: Meeting Minutes Generation)",
294
+ description="🎯 (真實 API 版本) 產生行政文件預覽。每次的結果都會不同。",
295
  flagging_mode="never",
296
  )
297
 
298
+ # 模組 B 介面 (Teaching Designer)
299
  lesson_plan_designer_interface = gr.Interface(
300
  fn=lesson_plan_designer,
301
  inputs=[
 
307
  gr.Textbox(label="可用設備 (Available Equipment)", value="平板電腦、投影設備、網路"),
308
  gr.Textbox(label="班級特性 (Class Characteristics)", value="班級組成多元,需考慮多樣化的史料呈現方式。"),
309
  ],
 
310
  outputs=gr.Markdown(label="AI 生成教案預覽 (Markdown Preview)"),
311
  title="教學 AI 設計器:教案與 Rubric 生成 (Teaching AI Designer: Lesson Plan & Rubric)",
312
+ description="📘 (真實 API 版本) 產生符合課綱精神的單元教案結構和評量規準預覽。",
313
  flagging_mode="never",
314
  )
315
 
316
+ # 將兩個模組整合到分頁介面中
317
  demo = gr.TabbedInterface(
318
  [admin_copilot_interface, lesson_plan_designer_interface],
319
  ["模組 A: 行政 Copilot", "模組 B: 教學設計器"],
320
+ title="CampusAI Suite (台灣校園 AI 文書/教學 MVP 演示) - 真實 API 版",
321
  theme=gr.themes.Soft()
322
  )
323
 
324
+ # --- 啟動應用程式 ---
325
  if __name__ == "__main__":
326
+ # 啟動時檢查 API 金鑰是否存在 (可選,但有助於除錯)
327
+ if not os.environ.get("GEMINI_API_KEY"):
328
+ print("警告:環境變數 'GEMINI_API_KEY' 尚未設定。")
329
+ print("應用程式將會啟動,但 API 呼叫將會失敗。")
330
+ print("請設定金鑰,例如:export GEMINI_API_KEY='您的金鑰'")
331
+
332
  demo.launch()
333