Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,12 +1,10 @@
|
|
| 1 |
import json
|
| 2 |
import time
|
| 3 |
-
import
|
| 4 |
-
import requests # <-- 請確保已安裝 'pip install requests'
|
| 5 |
-
import gradio as gr # <--- 匯入 Gradio
|
| 6 |
from typing import Dict, Any
|
| 7 |
|
| 8 |
# --- Markdown 轉換輔助函式 ---
|
| 9 |
-
# (此區域功能是將
|
| 10 |
|
| 11 |
def json_to_admin_markdown(data: Dict[str, Any]) -> str:
|
| 12 |
"""將「會議記錄」JSON 轉換為 Markdown 格式。"""
|
|
@@ -93,94 +91,97 @@ def json_to_lesson_plan_markdown(data: Dict[str, Any]) -> str:
|
|
| 93 |
except Exception as e:
|
| 94 |
return f"ERROR 產生 Markdown 預覽時出錯: {e}"
|
| 95 |
|
| 96 |
-
# ---
|
| 97 |
|
| 98 |
-
|
| 99 |
-
LLM_MODEL = "gemini-2.5-flash-preview-09-2025"
|
| 100 |
-
API_URL = f"https://generativelanguage.googleapis.com/v1beta/models/{LLM_MODEL}:generateContent"
|
| 101 |
-
|
| 102 |
-
def call_gemini_api(payload: Dict[str, Any]) -> str:
|
| 103 |
"""
|
| 104 |
-
|
|
|
|
| 105 |
"""
|
| 106 |
-
# 1. 從環境變數獲取 API 金鑰
|
| 107 |
-
api_key = os.environ.get("GEMINI_API_KEY")
|
| 108 |
-
if not api_key:
|
| 109 |
-
return json.dumps({
|
| 110 |
-
"error": "找不到 GEMINI_API_KEY",
|
| 111 |
-
"message": "請設定 GEMINI_API_KEY 環境變數才能使用 API。"
|
| 112 |
-
}, ensure_ascii=False)
|
| 113 |
-
|
| 114 |
-
# 2. 配置請求標頭和參數
|
| 115 |
-
headers = {"Content-Type": "application/json"}
|
| 116 |
-
params = {"key": api_key}
|
| 117 |
|
| 118 |
-
#
|
| 119 |
-
|
| 120 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 121 |
|
| 122 |
-
|
| 123 |
-
|
| 124 |
-
response_json = response.json()
|
| 125 |
-
# 提取文字內容 (即我們請求的 JSON 字串)
|
| 126 |
-
text_content = response_json['candidates'][0]['content']['parts'][0]['text']
|
| 127 |
-
return text_content
|
| 128 |
-
else:
|
| 129 |
-
# 如果 API 返回錯誤 (例如:金鑰錯誤、格式錯誤)
|
| 130 |
-
return json.dumps({
|
| 131 |
-
"error": f"API 錯誤: {response.status_code}",
|
| 132 |
-
"message": response.text
|
| 133 |
-
}, ensure_ascii=False)
|
| 134 |
-
|
| 135 |
-
except requests.exceptions.RequestException as e:
|
| 136 |
-
return json.dumps({
|
| 137 |
-
"error": "連線錯誤",
|
| 138 |
-
"message": str(e)
|
| 139 |
-
}, ensure_ascii=False)
|
| 140 |
-
except (KeyError, IndexError, TypeError) as e:
|
| 141 |
-
# 捕捉解析回應時的潛在錯誤
|
| 142 |
-
raw_response_text = response.text if 'response' in locals() else 'N/A'
|
| 143 |
-
return json.dumps({
|
| 144 |
-
"error": "未預期的 API 回應",
|
| 145 |
-
"message": f"無法解析回應: {e}. 收到的回應: {raw_response_text}"
|
| 146 |
-
}, ensure_ascii=False)
|
| 147 |
|
| 148 |
# --- 模組 A: 行政 Copilot 生成器 (Gradio 封裝) ---
|
| 149 |
-
# (
|
| 150 |
|
| 151 |
def admin_copilot_generator(template_id: str, topic: str, date: str, location: str, key_input: str) -> str:
|
| 152 |
"""
|
| 153 |
-
處理 Admin Copilot 的 UI 輸入,呼叫
|
| 154 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 155 |
system_prompt = (
|
| 156 |
"角色:台灣中學學務處行政書記\n"
|
| 157 |
"輸出:JSON(會議資訊、出席、重點、決議、待辦、負責人、期限)\n"
|
| 158 |
"格式規範:用詞正式、避免口語、保留專有名詞\n"
|
| 159 |
"限制:所有決議必須有負責人和明確期限。"
|
| 160 |
)
|
| 161 |
-
|
| 162 |
-
response_schema = {
|
| 163 |
-
"type": "OBJECT",
|
| 164 |
-
"properties": {
|
| 165 |
-
"文件類型 (Document Type)": {"type": "STRING"},
|
| 166 |
-
"meeting_info": {"type": "OBJECT", "properties": {"date": {"type": "STRING"}, "location": {"type": "STRING"}, "topic": {"type": "STRING"}}},
|
| 167 |
-
"attendees": {"type": "ARRAY", "items": {"type": "STRING"}},
|
| 168 |
-
"key_points": {"type": "ARRAY", "items": {"type": "STRING"}},
|
| 169 |
-
"resolutions": {
|
| 170 |
-
"type": "ARRAY",
|
| 171 |
-
"items": {
|
| 172 |
-
"type": "OBJECT",
|
| 173 |
-
"properties": {
|
| 174 |
-
"item": {"type": "STRING"},
|
| 175 |
-
"responsible": {"type": "STRING"},
|
| 176 |
-
"deadline": {"type": "STRING", "format": "date"}
|
| 177 |
-
}
|
| 178 |
-
}
|
| 179 |
-
},
|
| 180 |
-
"audit_note": {"type": "STRING"}
|
| 181 |
-
}
|
| 182 |
-
}
|
| 183 |
-
|
| 184 |
user_query = f"請生成一份會議記錄。主題: {topic}; 輸入重點(或逐字稿):{key_input}"
|
| 185 |
|
| 186 |
payload = {
|
|
@@ -192,88 +193,49 @@ def admin_copilot_generator(template_id: str, topic: str, date: str, location: s
|
|
| 192 |
}
|
| 193 |
}
|
| 194 |
|
| 195 |
-
# 呼叫
|
| 196 |
-
json_string =
|
| 197 |
|
| 198 |
try:
|
| 199 |
-
# 將 API 的 JSON 字串轉換為 Python 物件
|
| 200 |
data = json.loads(json_string)
|
| 201 |
-
|
| 202 |
-
# 如果 'data' 包含 'error' 鍵,直接顯示錯誤
|
| 203 |
if "error" in data:
|
| 204 |
-
return f"###
|
| 205 |
-
|
| 206 |
-
# 將
|
| 207 |
markdown_output = json_to_admin_markdown(data)
|
| 208 |
return markdown_output
|
| 209 |
|
| 210 |
except json.JSONDecodeError as e:
|
| 211 |
-
return f"### 處理回應時出錯\n\n無法解碼
|
| 212 |
-
except Exception as e:
|
| 213 |
-
return f"### 未預期錯誤\n\n**錯誤:** {e}\n\n**收到的資料:**\n```\n{json_string}\n```"
|
| 214 |
|
| 215 |
|
| 216 |
# --- 模組 B: 教學 AI 設計器 (Gradio 封裝) ---
|
|
|
|
| 217 |
|
| 218 |
def lesson_plan_designer(grade: str, subject: str, topic: str, hours: float, method: str, equipment: str, class_needs: str) -> str:
|
| 219 |
"""
|
| 220 |
-
處理教學設計器的 UI 輸入,呼叫
|
| 221 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 222 |
system_prompt = (
|
| 223 |
"角色:台灣國高中資深教師與課程設計師\n"
|
| 224 |
"輸出:JSON(教案標題、目標、課綱對齊、活動步驟、評量規準、差異化建議)\n"
|
| 225 |
"限制:活動分鏡以 15 分鐘粒度;至少 2 項形成性評量。\n"
|
| 226 |
"對齊:請將輸出中的 'curriculum_alignment' 欄位,對齊台灣課綱的關鍵能力/素養。"
|
| 227 |
)
|
| 228 |
-
|
| 229 |
-
# --- *** 已修正的 Schema *** ---
|
| 230 |
-
# 針對 'activities' 和 'rubric' 提供了更詳細的內部屬性
|
| 231 |
-
response_schema = {
|
| 232 |
-
"type": "OBJECT",
|
| 233 |
-
"properties": {
|
| 234 |
-
"文件類型 (Document Type)": {"type": "STRING"},
|
| 235 |
-
"lesson_plan_title": {"type": "STRING"},
|
| 236 |
-
"grade_level": {"type": "STRING"},
|
| 237 |
-
"curriculum_alignment": {"type": "ARRAY", "items": {"type": "STRING"}},
|
| 238 |
-
"learning_objectives": {"type": "ARRAY", "items": {"type": "STRING"}},
|
| 239 |
-
|
| 240 |
-
# 修正 1: 'activities' 內部的物件屬性
|
| 241 |
-
"activities": {
|
| 242 |
-
"type": "ARRAY",
|
| 243 |
-
"items": {
|
| 244 |
-
"type": "OBJECT",
|
| 245 |
-
"properties": {
|
| 246 |
-
"time_min": {"type": "NUMBER"},
|
| 247 |
-
"stage": {"type": "STRING"},
|
| 248 |
-
"method": {"type": "STRING"},
|
| 249 |
-
"description": {"type": "STRING"}
|
| 250 |
-
}
|
| 251 |
-
}
|
| 252 |
-
},
|
| 253 |
-
|
| 254 |
-
# 修正 2: 'rubric' 內部的物件屬性
|
| 255 |
-
"rubric": {
|
| 256 |
-
"type": "OBJECT",
|
| 257 |
-
"properties": {
|
| 258 |
-
"title": {"type": "STRING"},
|
| 259 |
-
"criteria": {
|
| 260 |
-
"type": "ARRAY",
|
| 261 |
-
"items": {
|
| 262 |
-
"type": "OBJECT",
|
| 263 |
-
"properties": {
|
| 264 |
-
"name": {"type": "STRING"},
|
| 265 |
-
"A": {"type": "STRING"},
|
| 266 |
-
"D": {"type": "STRING"}
|
| 267 |
-
}
|
| 268 |
-
}
|
| 269 |
-
}
|
| 270 |
-
}
|
| 271 |
-
},
|
| 272 |
-
|
| 273 |
-
"differentiation_advice": {"type": "STRING"}
|
| 274 |
-
}
|
| 275 |
-
}
|
| 276 |
-
# --- *** 修正結束 *** ---
|
| 277 |
|
| 278 |
user_query = (
|
| 279 |
f"請根據以下資訊設計一個單元教案、評量規數與差異化建議:\n"
|
|
@@ -293,28 +255,23 @@ def lesson_plan_designer(grade: str, subject: str, topic: str, hours: float, met
|
|
| 293 |
}
|
| 294 |
}
|
| 295 |
|
| 296 |
-
# 呼叫
|
| 297 |
-
json_string =
|
| 298 |
|
| 299 |
try:
|
| 300 |
-
# 將 API 的 JSON 字串轉換為 Python 物件
|
| 301 |
data = json.loads(json_string)
|
| 302 |
-
|
| 303 |
-
# 如果 'data' 包含 'error' 鍵,直接顯示錯誤
|
| 304 |
if "error" in data:
|
| 305 |
-
return f"###
|
| 306 |
|
| 307 |
-
# 將
|
| 308 |
markdown_output = json_to_lesson_plan_markdown(data)
|
| 309 |
return markdown_output
|
| 310 |
|
| 311 |
except json.JSONDecodeError as e:
|
| 312 |
-
return f"### 處理回應時出錯\n\n無法解碼
|
| 313 |
-
|
| 314 |
-
return f"### 未預期錯誤\n\n**錯誤:** {e}\n\n**收到的資料:**\n```\n{json_string}\n```"
|
| 315 |
|
| 316 |
# --- Gradio 介面定義 ---
|
| 317 |
-
# (UI 保持不變)
|
| 318 |
|
| 319 |
# 模組 A 介面 (Admin Copilot)
|
| 320 |
admin_copilot_interface = gr.Interface(
|
|
@@ -326,9 +283,9 @@ admin_copilot_interface = gr.Interface(
|
|
| 326 |
gr.Textbox(label="地點 (Location)", value="學務處會議室"),
|
| 327 |
gr.Textbox(label="輸入重點/逐字稿 (Key Input/Transcript)", value="討論期末獎懲核定程序。新生訓練場地佈置、人員編組確認。", lines=5),
|
| 328 |
],
|
| 329 |
-
outputs=gr.Markdown(label="AI
|
| 330 |
title="行政 Copilot:會議記錄生成 (Admin Copilot: Meeting Minutes Generation)",
|
| 331 |
-
description="🎯 (
|
| 332 |
flagging_mode="never",
|
| 333 |
)
|
| 334 |
|
|
@@ -344,9 +301,9 @@ lesson_plan_designer_interface = gr.Interface(
|
|
| 344 |
gr.Textbox(label="可用設備 (Available Equipment)", value="平板電腦、投影設備、網路"),
|
| 345 |
gr.Textbox(label="班級特性 (Class Characteristics)", value="班級組成多元,需考慮多樣化的史料呈現方式。"),
|
| 346 |
],
|
| 347 |
-
outputs=gr.Markdown(label="AI
|
| 348 |
title="教學 AI 設計器:教案與 Rubric 生成 (Teaching AI Designer: Lesson Plan & Rubric)",
|
| 349 |
-
description="📘 (
|
| 350 |
flagging_mode="never",
|
| 351 |
)
|
| 352 |
|
|
@@ -354,17 +311,11 @@ lesson_plan_designer_interface = gr.Interface(
|
|
| 354 |
demo = gr.TabbedInterface(
|
| 355 |
[admin_copilot_interface, lesson_plan_designer_interface],
|
| 356 |
["模組 A: 行政 Copilot", "模組 B: 教學設計器"],
|
| 357 |
-
title="CampusAI Suite (台灣校園 AI 文書/教學 MVP 演示) -
|
| 358 |
theme=gr.themes.Soft()
|
| 359 |
)
|
| 360 |
|
| 361 |
# --- 啟動應用程式 ---
|
| 362 |
if __name__ == "__main__":
|
| 363 |
-
# 啟動時檢查 API 金鑰是否存在 (可選,但有助於除錯)
|
| 364 |
-
if not os.environ.get("GEMINI_API_KEY"):
|
| 365 |
-
print("警告:環境變數 'GEMINI_API_KEY' 尚未設定。")
|
| 366 |
-
print("應用程式將會啟動,但 API 呼叫將會失敗。")
|
| 367 |
-
print("請設定金鑰,例如:export GEMINI_API_KEY='您的金鑰'")
|
| 368 |
-
|
| 369 |
demo.launch()
|
| 370 |
|
|
|
|
| 1 |
import json
|
| 2 |
import time
|
| 3 |
+
import gradio as gr # 匯入 Gradio
|
|
|
|
|
|
|
| 4 |
from typing import Dict, Any
|
| 5 |
|
| 6 |
# --- Markdown 轉換輔助函式 ---
|
| 7 |
+
# (此區域功能是將 JSON 轉換為易於閱讀的 Markdown)
|
| 8 |
|
| 9 |
def json_to_admin_markdown(data: Dict[str, Any]) -> str:
|
| 10 |
"""將「會議記錄」JSON 轉換為 Markdown 格式。"""
|
|
|
|
| 91 |
except Exception as e:
|
| 92 |
return f"ERROR 產生 Markdown 預覽時出錯: {e}"
|
| 93 |
|
| 94 |
+
# --- 模擬 API 呼叫函式 ---
|
| 95 |
|
| 96 |
+
def simulate_gemini_api_call(payload: Dict[str, Any], fields: Dict[str, Any]) -> str:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 97 |
"""
|
| 98 |
+
模擬 Gemini API 的結構化回應。
|
| 99 |
+
返回一個 JSON 字串。
|
| 100 |
"""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
|
| 102 |
+
# 模擬延遲
|
| 103 |
+
time.sleep(1.5)
|
| 104 |
+
|
| 105 |
+
system_instruction = payload.get('systemInstruction', {}).get('parts', [{}])[0].get('text', '')
|
| 106 |
+
|
| 107 |
+
# 根據系統提示 (System Prompt) 決定要模擬哪種輸出
|
| 108 |
+
|
| 109 |
+
if "台灣中學學務處行政書記" in system_instruction:
|
| 110 |
+
# 模擬 Admin Copilot (會議記錄) 輸出
|
| 111 |
+
mock_data = {
|
| 112 |
+
"文件類型 (Document Type)": "學務處會議記錄 (模擬)",
|
| 113 |
+
"meeting_info": {
|
| 114 |
+
"date": fields.get('date', '2025-01-10'),
|
| 115 |
+
"location": fields.get('location', '會議室'),
|
| 116 |
+
"topic": fields.get('topic', '模擬會議主題')
|
| 117 |
+
},
|
| 118 |
+
"attendees": ["校長", "學務主任", "教務主任", "輔導室主任"],
|
| 119 |
+
"key_points": [
|
| 120 |
+
f"基於使用者輸入「{fields.get('key_input', '...')}」進行討論。",
|
| 121 |
+
"新生訓練籌備工作已完成 80%。",
|
| 122 |
+
"確認場地佈置與人力編組。"
|
| 123 |
+
],
|
| 124 |
+
"resolutions": [
|
| 125 |
+
{"item": "發布期末獎懲正式公告", "responsible": "生輔組", "deadline": "2025-01-15"},
|
| 126 |
+
{"item": "新生訓練場地於前一日完成佈置", "responsible": "總務處", "deadline": "2025-08-20"}
|
| 127 |
+
],
|
| 128 |
+
"audit_note": "文件由 AI 模擬生成,符合校內寫作規範。"
|
| 129 |
+
}
|
| 130 |
+
return json.dumps(mock_data, ensure_ascii=False)
|
| 131 |
+
|
| 132 |
+
elif "台灣國高中資深教師與課程設計師" in system_instruction:
|
| 133 |
+
# 模擬 Teaching Designer (教案) 輸出
|
| 134 |
+
mock_data = {
|
| 135 |
+
"文件類型 (Document Type)": "單元教案與評量規準 (模擬)",
|
| 136 |
+
"lesson_plan_title": f"【{fields.get('subject', 'N/A')}】探索:{fields.get('topic', 'N/A')} ({int(fields.get('hours', 0))} 節課)",
|
| 137 |
+
"grade_level": fields.get('grade', 'N/A'),
|
| 138 |
+
"curriculum_alignment": ["A2 系統思考與解決問題 (課綱素養)", "B3 規劃執行與創新應變"],
|
| 139 |
+
"learning_objectives": ["學生能說明 X 的核心概念。", "學生能應用 Y 技能進行分析。", "學生能透過 Z 進行小組協作。"],
|
| 140 |
+
"activities": [
|
| 141 |
+
{"time_min": 15, "stage": "引起動機", "method": "提問法", "description": "使用真實案例影片,引導學生思考主題。"},
|
| 142 |
+
{"time_min": 30, "stage": "主要活動一", "method": fields.get('method', '合作學習'), "description": "分組進行資料搜集與主題探究 (使用 {equipment})。".format(equipment=fields.get('equipment', 'N/A'))},
|
| 143 |
+
{"time_min": 25, "stage": "成果發表", "method": "發表與問答", "description": "各組上台報告初步發現。"},
|
| 144 |
+
{"time_min": 20, "stage": "總結與評量", "method": "形成性評量", "description": "教師總結,並進行快速測驗。"}
|
| 145 |
+
],
|
| 146 |
+
"rubric": {
|
| 147 |
+
"title": "單元評量規準 (4 級分)",
|
| 148 |
+
"criteria": [
|
| 149 |
+
{"name": "概念理解", "A": "能清晰且準確地解釋所有核心概念。", "D": "僅能回答基礎問題。"},
|
| 150 |
+
{"name": "團隊協作", "A": "積極主動領導團隊,有效分工。", "D": "未參與團隊討論。"},
|
| 151 |
+
{"name": "資料分析", "A": "能運用多種史料進行深入分析。", "D": "僅列出資料,未加分析。"}
|
| 152 |
+
]
|
| 153 |
+
},
|
| 154 |
+
"differentiation_advice": f"針對特性「{fields.get('class_needs', 'N/A')}」,建議提供補充詞彙卡或鷹架提問單。"
|
| 155 |
+
}
|
| 156 |
+
return json.dumps(mock_data, ensure_ascii=False)
|
| 157 |
|
| 158 |
+
else:
|
| 159 |
+
return json.dumps({"error": "未知的模擬任務", "message": "系統提示不符"}, ensure_ascii=False)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 160 |
|
| 161 |
# --- 模組 A: 行政 Copilot 生成器 (Gradio 封裝) ---
|
| 162 |
+
# (已修改為使用 simulate_gemini_api_call)
|
| 163 |
|
| 164 |
def admin_copilot_generator(template_id: str, topic: str, date: str, location: str, key_input: str) -> str:
|
| 165 |
"""
|
| 166 |
+
處理 Admin Copilot 的 UI 輸入,呼叫「模擬」 API,並轉換為 Markdown。
|
| 167 |
"""
|
| 168 |
+
|
| 169 |
+
# 這些是模擬 API 需要的欄位
|
| 170 |
+
fields = {
|
| 171 |
+
"topic": topic,
|
| 172 |
+
"date": date,
|
| 173 |
+
"location": location,
|
| 174 |
+
"key_input": key_input
|
| 175 |
+
}
|
| 176 |
+
|
| 177 |
+
# 這些是建構 payload 所需的 (模擬時僅用於判斷任務類型)
|
| 178 |
system_prompt = (
|
| 179 |
"角色:台灣中學學務處行政書記\n"
|
| 180 |
"輸出:JSON(會議資訊、出席、重點、決議、待辦、負責人、期限)\n"
|
| 181 |
"格式規範:用詞正式、避免口語、保留專有名詞\n"
|
| 182 |
"限制:所有決議必須有負責人和明確期限。"
|
| 183 |
)
|
| 184 |
+
response_schema = { "type": "OBJECT" } # 模擬時不需要完整 Schema
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 185 |
user_query = f"請生成一份會議記錄。主題: {topic}; 輸入重點(或逐字稿):{key_input}"
|
| 186 |
|
| 187 |
payload = {
|
|
|
|
| 193 |
}
|
| 194 |
}
|
| 195 |
|
| 196 |
+
# 呼叫模擬 API
|
| 197 |
+
json_string = simulate_gemini_api_call(payload, fields)
|
| 198 |
|
| 199 |
try:
|
|
|
|
| 200 |
data = json.loads(json_string)
|
|
|
|
|
|
|
| 201 |
if "error" in data:
|
| 202 |
+
return f"### 模擬錯誤\n\n**訊息:**\n```\n{data.get('message')}\n```"
|
| 203 |
+
|
| 204 |
+
# 將模擬 JSON 轉換為 Markdown
|
| 205 |
markdown_output = json_to_admin_markdown(data)
|
| 206 |
return markdown_output
|
| 207 |
|
| 208 |
except json.JSONDecodeError as e:
|
| 209 |
+
return f"### 處理回應時出錯\n\n無法解碼模擬 JSON。\n\n**收到的回應:**\n```\n{json_string}\n```"
|
|
|
|
|
|
|
| 210 |
|
| 211 |
|
| 212 |
# --- 模組 B: 教學 AI 設計器 (Gradio 封裝) ---
|
| 213 |
+
# (已修改為使用 simulate_gemini_api_call)
|
| 214 |
|
| 215 |
def lesson_plan_designer(grade: str, subject: str, topic: str, hours: float, method: str, equipment: str, class_needs: str) -> str:
|
| 216 |
"""
|
| 217 |
+
處理教學設計器的 UI 輸入,呼叫「模擬」 API,並轉換為 Markdown。
|
| 218 |
"""
|
| 219 |
+
|
| 220 |
+
# 這些是模擬 API 需要的欄位
|
| 221 |
+
fields = {
|
| 222 |
+
"grade": grade,
|
| 223 |
+
"subject": subject,
|
| 224 |
+
"topic": topic,
|
| 225 |
+
"hours": hours,
|
| 226 |
+
"method": method,
|
| 227 |
+
"equipment": equipment,
|
| 228 |
+
"class_needs": class_needs
|
| 229 |
+
}
|
| 230 |
+
|
| 231 |
+
# 這些是建構 payload 所需的 (模擬時僅用於判斷任務類型)
|
| 232 |
system_prompt = (
|
| 233 |
"角色:台灣國高中資深教師與課程設計師\n"
|
| 234 |
"輸出:JSON(教案標題、目標、課綱對齊、活動步驟、評量規準、差異化建議)\n"
|
| 235 |
"限制:活動分鏡以 15 分鐘粒度;至少 2 項形成性評量。\n"
|
| 236 |
"對齊:請將輸出中的 'curriculum_alignment' 欄位,對齊台灣課綱的關鍵能力/素養。"
|
| 237 |
)
|
| 238 |
+
response_schema = { "type": "OBJECT" } # 模擬時不需要完整 Schema
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 239 |
|
| 240 |
user_query = (
|
| 241 |
f"請根據以下資訊設計一個單元教案、評量規數與差異化建議:\n"
|
|
|
|
| 255 |
}
|
| 256 |
}
|
| 257 |
|
| 258 |
+
# 呼叫模擬 API
|
| 259 |
+
json_string = simulate_gemini_api_call(payload, fields)
|
| 260 |
|
| 261 |
try:
|
|
|
|
| 262 |
data = json.loads(json_string)
|
|
|
|
|
|
|
| 263 |
if "error" in data:
|
| 264 |
+
return f"### 模擬錯誤\n\n**訊息:**\n```\n{data.get('message')}\n```"
|
| 265 |
|
| 266 |
+
# 將模擬 JSON 轉換為 Markdown
|
| 267 |
markdown_output = json_to_lesson_plan_markdown(data)
|
| 268 |
return markdown_output
|
| 269 |
|
| 270 |
except json.JSONDecodeError as e:
|
| 271 |
+
return f"### 處理回應時出錯\n\n無法解碼模擬 JSON。\n\n**收到的回應:**\n```\n{json_string}\n```"
|
| 272 |
+
|
|
|
|
| 273 |
|
| 274 |
# --- Gradio 介面定義 ---
|
|
|
|
| 275 |
|
| 276 |
# 模組 A 介面 (Admin Copilot)
|
| 277 |
admin_copilot_interface = gr.Interface(
|
|
|
|
| 283 |
gr.Textbox(label="地點 (Location)", value="學務處會議室"),
|
| 284 |
gr.Textbox(label="輸入重點/逐字稿 (Key Input/Transcript)", value="討論期末獎懲核定程序。新生訓練場地佈置、人員編組確認。", lines=5),
|
| 285 |
],
|
| 286 |
+
outputs=gr.Markdown(label="AI 模擬文件預覽 (Markdown Preview)"),
|
| 287 |
title="行政 Copilot:會議記錄生成 (Admin Copilot: Meeting Minutes Generation)",
|
| 288 |
+
description="🎯 (模擬演示版) 產生行政文件預覽。此版本不需 API 金鑰,使用固定的模擬資料。",
|
| 289 |
flagging_mode="never",
|
| 290 |
)
|
| 291 |
|
|
|
|
| 301 |
gr.Textbox(label="可用設備 (Available Equipment)", value="平板電腦、投影設備、網路"),
|
| 302 |
gr.Textbox(label="班級特性 (Class Characteristics)", value="班級組成多元,需考慮多樣化的史料呈現方式。"),
|
| 303 |
],
|
| 304 |
+
outputs=gr.Markdown(label="AI 模擬教案預覽 (Markdown Preview)"),
|
| 305 |
title="教學 AI 設計器:教案與 Rubric 生成 (Teaching AI Designer: Lesson Plan & Rubric)",
|
| 306 |
+
description="📘 (模擬演示版) 產生符合課綱精神的單元教案結構和評量規準預覽。",
|
| 307 |
flagging_mode="never",
|
| 308 |
)
|
| 309 |
|
|
|
|
| 311 |
demo = gr.TabbedInterface(
|
| 312 |
[admin_copilot_interface, lesson_plan_designer_interface],
|
| 313 |
["模組 A: 行政 Copilot", "模組 B: 教學設計器"],
|
| 314 |
+
title="CampusAI Suite (台灣校園 AI 文書/教學 MVP 演示) - 模擬版",
|
| 315 |
theme=gr.themes.Soft()
|
| 316 |
)
|
| 317 |
|
| 318 |
# --- 啟動應用程式 ---
|
| 319 |
if __name__ == "__main__":
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 320 |
demo.launch()
|
| 321 |
|