File size: 10,667 Bytes
cee989c
 
8bdd359
cee989c
 
 
8bdd359
cee989c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
30f214c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8bdd359
30f214c
8bdd359
30f214c
 
 
 
 
 
 
 
8bdd359
30f214c
 
 
 
 
 
8bdd359
 
 
30f214c
 
 
 
 
 
8bdd359
30f214c
 
 
 
 
 
 
8bdd359
 
30f214c
8bdd359
30f214c
8985814
30f214c
8bdd359
30f214c
8bdd359
 
30f214c
 
 
 
 
 
 
 
 
 
 
8bdd359
30f214c
 
8985814
30f214c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
8bdd359
30f214c
 
 
 
 
 
 
8bdd359
30f214c
8bdd359
30f214c
 
 
 
 
 
 
 
 
 
 
 
 
8bdd359
30f214c
8bdd359
 
30f214c
 
 
 
 
 
 
 
 
 
8985814
30f214c
 
 
8bdd359
8985814
8bdd359
 
30f214c
 
 
 
 
 
 
 
e0c33d5
 
a6b7f72
8bdd359
a6b7f72
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
import json
import time
from typing import Dict, Any
import gradio as gr

# --- Simulation Setup for LLM API ---
# This section simulates the core AI generation logic without requiring a live API key.
LLM_MODEL = "gemini-2.5-flash-preview-09-2025"
API_KEY = "" # API Key Placeholder

def simulate_gemini_api_call(payload: Dict[str, Any], fields: Dict[str, Any]) -> Dict[str, Any]:
    """
    Simulates a structured response from the Gemini API based on the task type.
    In a real application, this function would make a fetch call to the Gemini API.
    """
    
    # Simulate API latency
    time.sleep(1.0)
    
    user_query = payload['contents'][0]['parts'][0]['text']
    system_instruction = payload.get('systemInstruction', {}).get('parts', [{}])[0].get('text', 'No system instruction')
    
    # Check system instruction to determine the output type (Admin or Teaching)
    if "台灣中學學務處行政書記" in system_instruction:
        # Simulate Admin Copilot (Meeting Minutes) output
        mock_text_result = json.dumps({
            "文件類型 (Document Type)": "學務處會議記錄 (Academic Affairs Meeting Minutes)",
            "meeting_info": {
                "date": fields.get('date', '2025-01-10'),
                "location": fields.get('location', '學務處會議室'),
                "topic": fields.get('topic', '模擬會議主題')
            },
            "attendees": ["校長", "學務主任", "衛生組長", "生輔組長"],
            "key_points": [
                "期末獎懲核定程序已完成,共核定 30 件。建議將名單呈報校長核閱。",
                "新生訓練場地佈置進度達 80%,物資清單已交付總務處採購。",
                f"重點輸入: {fields.get('key_input', 'N/A')}"
            ],
            "resolutions": [
                {"item": "發布正式期末獎懲公告。", "responsible": "教務處", "deadline": "2025-01-15"},
                {"item": "新生訓練場地佈置於活動前一天完成驗收。", "responsible": "總務處", "deadline": "2025-08-20"}
            ],
            "audit_note": "文件根據校內行政公文標準格式生成。"
        }, ensure_ascii=False, indent=2)
        
    elif "台灣國高中資深教師與課程設計師" in system_instruction:
        # Simulate Teaching Designer (Lesson Plan & Rubric) output
        mock_text_result = json.dumps({
            "文件類型 (Document Type)": "單元教案與評量規準 (Lesson Plan & Rubric)",
            "lesson_plan_title": f"【{fields.get('subject', 'N/A')}】探索 {fields.get('topic', 'N/A')} ({fields.get('hours', 0)} 課時)",
            "grade_level": fields.get('grade', 'N/A'),
            "curriculum_alignment": ["A2 邏輯推理與批判思辨", "B3 獨立思考與探究精神"],
            "learning_objectives": ["學生能解釋核心概念 X。", "學生能應用方法 Y 進行分析。", "學生能製作報告Z進行表達。"],
            "activities": [
                {"time_min": 15, "stage": "引導", "method": "提問式教學", "description": "使用新聞案例引導核心概念。"},
                {"time_min": 30, "stage": "活動一", "method": "合作學習", "description": "分組完成專題研究和實作練習。"},
            ],
            "rubric": {
                "title": "單元評量規準 (4 級 X 4 指標)",
                "criteria": [
                    {"name": "概念理解", "A": "清晰精確地解釋所有核心概念。", "D": "只能回答簡單問題。"},
                    {"name": "協作能力", "A": "積極領導團隊完成任務。", "D": "未參與討論。"}
                ]
            },
            "differentiation_advice": f"根據班級特性 ({fields.get('class_needs', 'N/A')}),建議提供圖像化教材並進行分組輔導。"
        }, ensure_ascii=False, indent=2)
        
    else:
        mock_text_result = json.dumps({"error": "Unknown or missing task instruction."})


    # Return the simulated API response structure
    return {
        "candidates": [{
            "content": {
                "parts": [{ "text": mock_text_result }]
            },
            "groundingMetadata": {}
        }]
    }

# --- Module A: Admin Copilot Generator (Gradio Wrapper) ---

def admin_copilot_generator(template_id: str, topic: str, date: str, location: str, key_input: str) -> str:
    """
    Handles the Admin Copilot UI inputs and calls the simulation.
    """
    fields = {
        "topic": topic,
        "date": date,
        "location": location,
        "key_input": key_input
    }

    # System Prompt defined for the Admin Copilot
    system_prompt = (
        "角色:台灣中學學務處行政書記\n"
        "輸出:JSON(會議資訊、出席、重點、決議、待辦、負責人、期限)\n"
        "格式規範:用詞正式、避免口語、保留專有名詞\n"
        "限制:所有決議必須有負責人和明確期限。"
    )

    # Response Schema is implicitly defined but would be included in a real API call.
    # The Gradio JSON output will just display the resulting JSON string.
    
    user_query = f"請生成一份會議記錄。主題: {topic}; 輸入重點(或逐字稿):{key_input}"
    
    payload = {
        "contents": [{ "parts": [{ "text": user_query }] }],
        "systemInstruction": { "parts": [{ "text": system_prompt }] },
        # Simplified generationConfig for simulation
        "generationConfig": { "responseMimeType": "application/json" }
    }

    api_response = simulate_gemini_api_call(payload, fields)
    
    try:
        json_string = api_response['candidates'][0]['content']['parts'][0]['text']
        # For Gradio, we return the JSON string directly
        return json_string
    except (KeyError, json.JSONDecodeError) as e:
        return f"ERROR: Failed to parse LLM structured output. {e}"

# --- Module B: Teaching AI Designer (Gradio Wrapper) ---

def lesson_plan_designer(grade: str, subject: str, topic: str, hours: float, method: str, equipment: str, class_needs: str) -> str:
    """
    Handles the Teaching Designer UI inputs and calls the simulation.
    Note: hours is float because Gradio Slider output is float
    """
    fields = {
        "grade": grade,
        "subject": subject,
        "topic": topic,
        "hours": int(hours), # Convert back to int for display consistency
        "method": method,
        "equipment": equipment,
        "class_needs": class_needs
    }
    
    # System Prompt defined for the Teaching Designer
    system_prompt = (
        "角色:台灣國高中資深教師與課程設計師\n"
        "輸出:JSON(教案標題、目標、課綱對齊、活動步驟、評量規準、差異化建議)\n"
        "限制:活動分鏡以 15 分鐘粒度;至少 2 項形成性評量。\n"
        "對齊:請將輸出中的 'curriculum_alignment' 欄位,對齊台灣課綱的關鍵能力/素養。"
    )
    
    user_query = (
        f"請根據以下資訊設計一個單元教案、評量規準和差異化建議:\n"
        f"年級/學科/單元主題: {grade}/{subject}/{topic}\n"
        f"課時數: {int(hours)} 節\n"
        f"教學法偏好: {method}\n"
        f"可用設備: {equipment}\n"
        f"班級特性: {class_needs}"
    )

    payload = {
        "contents": [{ "parts": [{ "text": user_query }] }],
        "systemInstruction": { "parts": [{ "text": system_prompt }] },
        # Simplified generationConfig for simulation
        "generationConfig": { "responseMimeType": "application/json" }
    }

    api_response = simulate_gemini_api_call(payload, fields)
    
    try:
        json_string = api_response['candidates'][0]['content']['parts'][0]['text']
        return json_string
    except (KeyError, json.JSONDecodeError) as e:
        return f"ERROR: Failed to parse LLM structured output. {e}"

# --- Gradio Interface Definition ---

# Module A Interface (Admin Copilot)
admin_copilot_interface = gr.Interface(
    fn=admin_copilot_generator,
    inputs=[
        gr.Textbox(label="模板 ID (Template ID - Fixed for MVP)", value="meeting_minutes_standard", interactive=False),
        gr.Textbox(label="會議主題 (Meeting Topic)", value="學務處期末獎懲與新生訓練籌備會議"),
        gr.Textbox(label="日期 (Date)", value="2025-01-10"),
        gr.Textbox(label="地點 (Location)", value="學務處會議室"),
        gr.Textbox(label="輸入重點/逐字稿 (Key Input/Transcript)", value="討論期末獎懲核定程序。新生訓練場地佈置、人員編組確認。", lines=5),
    ],
    outputs=gr.JSON(label="AI 生成結構化 JSON (原始資料)"),
    title="行政 Copilot:會議記錄生成 (Admin Copilot: Meeting Minutes Generation)",
    description="🎯 生成格式嚴謹的行政文件 JSON 結構。",
    flagging_mode="never", # Updated from allow_flagging
)

# Module B Interface (Teaching Designer)
lesson_plan_designer_interface = gr.Interface(
    fn=lesson_plan_designer,
    inputs=[
        gr.Dropdown(label="年級 (Grade)", choices=["國中", "高中", "國小"], value="高中"),
        gr.Textbox(label="學科 (Subject)", value="歷史"),
        gr.Textbox(label="單元主題 (Unit Topic)", value="從茶葉看全球化:17-19世紀的貿易網絡"),
        gr.Slider(label="課時數 (Number of Sessions)", minimum=1, maximum=10, step=1, value=4),
        gr.Dropdown(label="教學法偏好 (Pedagogy Preference)", choices=["探究式、PBL", "翻轉教學", "合作學習", "講述法"], value="探究式、PBL"),
        gr.Textbox(label="可用設備 (Available Equipment)", value="平板電腦、投影設備、網路"),
        gr.Textbox(label="班級特性 (Class Characteristics)", value="班級組成多元,需考慮多樣化的史料呈現方式。"),
    ],
    outputs=gr.JSON(label="AI 生成教案與評量規準 JSON (原始資料)"),
    title="教學 AI 設計器:教案與 Rubric 生成 (Teaching AI Designer: Lesson Plan & Rubric)",
    description="📘 生成符合課綱精神的單元教案結構和評量規準 JSON。",
    flagging_mode="never", # Updated from allow_flagging
)

# Integrate the two modules into a Tabbed Interface
demo = gr.TabbedInterface(
    [admin_copilot_interface, lesson_plan_designer_interface],
    ["模組 A: 行政 Copilot", "模組 B: 教學設計器"],
    title="CampusAI Suite (台灣校園 AI 文書/教學 MVP 演示)",
    theme=gr.themes.Soft()
)

# --- Launch the application ---
# This is required for the application to start in the container
if __name__ == "__main__":
    demo.launch()