steveyehtw commited on
Commit
a22790d
·
1 Parent(s): 50e1d54

Reorganize 2026_AI camp: Traditional names, Gemini key prompt, deps

Browse files

- Add main.py entries and fix student scripts for Arcade Hub
- Traditional Chinese project folder names; Gemini terminal API key UI
- requirements: pandas, matplotlib, bs4, lxml, google-genai SDKs

This view is limited to 50 files because it contains too many changes.   See raw diff
Files changed (50) hide show
  1. projects/2026_AI營1/劉宗穎/{DataFrame指令练习 → DataFrame指令練習}/d1_1_pandas_intro.py +37 -36
  2. projects/2026_AI營1/劉宗穎/DataFrame指令練習/main.py +18 -0
  3. projects/2026_AI營1/劉宗穎/{DataFrame指令练习 → DataFrame指令練習}/taipei_weather_2026.csv +0 -0
  4. projects/2026_AI營1/劉宗穎/{ai_api串接练习 → ai_api串接練習}/d2_1_gemini.py +62 -40
  5. projects/2026_AI營1/劉宗穎/ai_api串接練習/main.py +18 -0
  6. projects/2026_AI營1/劉宗穎/{mock_密室逃脱游戏 → mock_密室逃脫遊戲}/d2_3_chatbot_memory_game_template.py +0 -0
  7. projects/2026_AI營1/劉宗穎/mock_密室逃脫遊戲/main.py +18 -0
  8. projects/2026_AI營1/劉宗穎/{mock_关键词抓取 → mock_關鍵詞抓取}/d2_3_chatbot_memory_mock.py +0 -0
  9. projects/2026_AI營1/劉宗穎/mock_關鍵詞抓取/main.py +18 -0
  10. projects/2026_AI營1/劉宗穎/{mock&AI_api双模式 → mock與AI_api雙模式}/d2_3_chatbot_memory.py +124 -95
  11. projects/2026_AI營1/劉宗穎/{mock&AI_api双模式 → mock與AI_api雙模式}/d2_4_chatbot_memory.py +158 -129
  12. projects/2026_AI營1/劉宗穎/mock與AI_api雙模式/main.py +18 -0
  13. projects/2026_AI營1/劉宗穎/{三指捏游戏 → 三指捏遊戲}/d5_pinch_challenge_solution.py +216 -217
  14. projects/2026_AI營1/劉宗穎/三指捏遊戲/main.py +18 -0
  15. projects/2026_AI營1/劉宗穎/{人脸辨识 → 人臉辨識}/d3_2_face_detection.py +0 -0
  16. projects/2026_AI營1/劉宗穎/{人脸辨识 → 人臉辨識}/d3_2_face_detection2.py +0 -0
  17. projects/2026_AI營1/劉宗穎/人臉辨識/main.py +18 -0
  18. projects/2026_AI營1/劉宗穎/人臉辨識/onepiece(2).jpeg +3 -0
  19. projects/2026_AI營1/劉宗穎/{咖啡店分析图表 → 咖啡店分析圖表}/coffee_sales_analysis.png +0 -0
  20. projects/2026_AI營1/劉宗穎/{咖啡店分析图表 → 咖啡店分析圖表}/coffee_sales_data.csv +0 -0
  21. projects/2026_AI營1/劉宗穎/{咖啡店分析图表 → 咖啡店分析圖表}/d1_4_visualization_coffee.py +31 -1
  22. projects/2026_AI營1/劉宗穎/咖啡店分析圖表/main.py +18 -0
  23. projects/2026_AI營1/劉宗穎/嘴巴偵測/main.py +18 -0
  24. projects/2026_AI營1/劉宗穎/嘴巴偵測/onepiece(2).jpeg +3 -0
  25. projects/2026_AI營1/劉宗穎/{嘴巴侦测 → 嘴巴偵測}/step3_mouth_ratio.py +113 -113
  26. projects/2026_AI營1/劉宗穎/{天气资料图表 → 天氣資料圖表}/d1_3_visualization.py +117 -87
  27. projects/2026_AI營1/劉宗穎/天氣資料圖表/main.py +18 -0
  28. projects/2026_AI營1/劉宗穎/天氣資料圖表/onepiece(2).jpeg +3 -0
  29. projects/2026_AI營1/劉宗穎/{天气资料图表 → 天氣資料圖表}/taipei_weather_2026.csv +0 -0
  30. projects/2026_AI營1/劉宗穎/{影像处理 → 影像處理}/d3_1_opencv_basic.py +110 -70
  31. projects/2026_AI營1/劉宗穎/影像處理/main.py +18 -0
  32. projects/2026_AI營1/劉宗穎/影像處理/onepiece(2).jpeg +3 -0
  33. projects/2026_AI營1/劉宗穎/{手指画画游戏 → 手指畫畫遊戲}/draw_game_student.py +2 -1
  34. projects/2026_AI營1/劉宗穎/手指畫畫遊戲/main.py +18 -0
  35. projects/2026_AI營1/劉宗穎/手指畫畫遊戲/onepiece(2).jpeg +3 -0
  36. projects/2026_AI營1/劉宗穎/{爬虫分析 → 爬蟲分析}/d4_1_ex1_books.py +0 -0
  37. projects/2026_AI營1/劉宗穎/{爬虫分析 → 爬蟲分析}/d4_1_ex2_ptt.py +0 -0
  38. projects/2026_AI營1/劉宗穎/{爬虫分析 → 爬蟲分析}/d4_1_ex3_ithelp.py +0 -0
  39. projects/2026_AI營1/劉宗穎/爬蟲分析/main.py +26 -0
  40. projects/2026_AI營1/劉宗穎/爬蟲分析_PTT/d4_1_ex2_ptt.py +81 -0
  41. projects/2026_AI營1/劉宗穎/爬蟲分析_PTT/main.py +18 -0
  42. projects/2026_AI營1/劉宗穎/爬蟲分析_books/d4_1_ex1_books.py +94 -0
  43. projects/2026_AI營1/劉宗穎/爬蟲分析_books/main.py +18 -0
  44. projects/2026_AI營1/劉宗穎/爬蟲分析_ithelp/d4_1_ex3_ithelp.py +98 -0
  45. projects/2026_AI營1/劉宗穎/爬蟲分析_ithelp/main.py +18 -0
  46. projects/2026_AI營1/劉宗穎/{爬虫分析与AI报告 → 爬蟲分析與AI報告}/d4_1_web_scraping.py +0 -0
  47. projects/2026_AI營1/劉宗穎/{爬虫分析与AI报告 → 爬蟲分析與AI報告}/d4_2_agent_task_chain.py +33 -2
  48. projects/2026_AI營1/劉宗穎/{爬虫分析与AI报告 → 爬蟲分析與AI報告}/d4_2_ex2_crawler_ai.py +33 -2
  49. projects/2026_AI營1/劉宗穎/爬蟲分析與AI報告/main.py +18 -0
  50. projects/2026_AI營1/劉宗穎/眼睛距離偵測/main.py +18 -0
projects/2026_AI營1/劉宗穎/{DataFrame指令练习 → DataFrame指令練習}/d1_1_pandas_intro.py RENAMED
@@ -1,37 +1,38 @@
1
- # d1_1_pandas_intro.py
2
- # Day 1 學員填空:Pandas 基礎(完成 TODO 後執行)
3
- # cd examples/Day1/student && python3 d1_1_pandas_intro.py
4
- # 參考答案:../Teacher/d1_1_pandas_intro.py
5
-
6
- import pandas as pd
7
-
8
- df = pd.read_csv("taipei_weather_2026.csv")
9
- print("=== 數據載入成功! ===")
10
-
11
- print("\n--- 1. head ---")
12
- print(df.head())
13
-
14
- print("\n--- 2. info ---")
15
- df.info()
16
-
17
- print("\n--- 3. describe ---")
18
- print(df.describe())
19
-
20
- print("\n--- 4. Temperature 前 5 筆 ---")
21
- print(df['Temperature'].head())
22
-
23
- print("\n--- 5. Date 與 Rainfall ---")
24
- print(df[['Rainfall', 'Date']].head())
25
-
26
- print("\n--- 挑戰:最高溫與平均濕度 ---")
27
- max_temp = df['Temperature'].max()
28
- avg_humidity = df['Humidity'].mean()
29
- min_rainfall = df['Rainfall'].min()
30
- print(f"最高溫度:{max_temp}°C")
31
- print(f"平均濕度:{avg_humidity:.2f}%")
32
- print(f"最低降雨:{min_rainfall:.2f}%")
33
-
34
- print("\n--- whether it is good for egyptians")
35
-
36
- egy = df[(df['Temperature'] > 27) & (df['Rainfall'] == 0)]
 
37
  print(egy)
 
1
+ import os
2
+ # d1_1_pandas_intro.py
3
+ # Day 1 學員填空:Pandas 基礎(完成 TODO 後執行)
4
+ # cd examples/Day1/student && python3 d1_1_pandas_intro.py
5
+ # 參考答案:../Teacher/d1_1_pandas_intro.py
6
+
7
+ import pandas as pd
8
+
9
+ df = pd.read_csv(os.path.join(os.path.dirname(__file__), "taipei_weather_2026.csv"))
10
+ print("=== 數據載入成功! ===")
11
+
12
+ print("\n--- 1. head ---")
13
+ print(df.head())
14
+
15
+ print("\n--- 2. info ---")
16
+ df.info()
17
+
18
+ print("\n--- 3. describe ---")
19
+ print(df.describe())
20
+
21
+ print("\n--- 4. Temperature 前 5 筆 ---")
22
+ print(df['Temperature'].head())
23
+
24
+ print("\n--- 5. Date 與 Rainfall ---")
25
+ print(df[['Rainfall', 'Date']].head())
26
+
27
+ print("\n--- 挑戰:最高溫與平均濕度 ---")
28
+ max_temp = df['Temperature'].max()
29
+ avg_humidity = df['Humidity'].mean()
30
+ min_rainfall = df['Rainfall'].min()
31
+ print(f"最高溫度:{max_temp}°C")
32
+ print(f"平均濕度:{avg_humidity:.2f}%")
33
+ print(f"最低降雨:{min_rainfall:.2f}%")
34
+
35
+ print("\n--- whether it is good for egyptians")
36
+
37
+ egy = df[(df['Temperature'] > 27) & (df['Rainfall'] == 0)]
38
  print(egy)
projects/2026_AI營1/劉宗穎/DataFrame指令練習/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Telearn Arcade Hub 入口檔(自動產生)
4
+ 實際執行: d1_1_pandas_intro.py
5
+ 規格: projects/{班級}/{學生}/{專案}/main.py
6
+ """
7
+ from pathlib import Path
8
+ import os
9
+ import runpy
10
+ import sys
11
+
12
+ _ROOT = Path(__file__).resolve().parent
13
+ os.chdir(_ROOT)
14
+ if str(_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_ROOT))
16
+
17
+ # 執行原始學員腳本
18
+ runpy.run_path(str(_ROOT / 'd1_1_pandas_intro.py'), run_name="__main__")
projects/2026_AI營1/劉宗穎/{DataFrame指令练习 → DataFrame指令練習}/taipei_weather_2026.csv RENAMED
File without changes
projects/2026_AI營1/劉宗穎/{ai_api串接练习 → ai_api串接練習}/d2_1_gemini.py RENAMED
@@ -1,41 +1,63 @@
1
- # d2_1_gemini.py — Day2 學員填空
2
- # cd examples/Day2/Student && python3 d2_1_gemini.py
3
- # 參考答案:../Teacher/d2_1_gemini.py
4
-
5
- import os
6
- from google import genai
7
-
8
- # === 【學員填空練習點 1:設置與載入 API Key】 ===
9
- # TODO: 嘗試從環境變數 GEMINI_API_KEY 取得 API Key。如果未設定,則使用 input() 提示手動輸入。
10
- api_key = os.environ.get("GEMINI_API_KEY", "AQ.Ab8RN6LgriztRWiwQOdkK1wXmmfs4AzlZKSQsP2X0cnjzaMCCg")
11
-
12
- if not api_key:
13
- print("[提示] 未檢測到環境變數 GEMINI_API_KEY。")
14
- print(" 請先設定:export GEMINI_API_KEY=\"your_key\"")
15
- print(" 或於下方手動輸入(僅供本機測試,勿寫死在檔案中上傳):")
16
- api_key = input("API Key: ").strip()
17
- if not api_key:
18
- print("[結束] 未提供 API Key。可改練習 d2_1_ollama_local.py。")
19
- exit(0)
20
-
21
- # === 【學員填空練習點 2:設定模型名稱】 ===
22
- # TODO: 設定使用的 Gemini 模型名稱(例如 'gemini-3.1-flash-lite')
23
- model_name = "gemini-3.1-flash-lite"
24
-
25
- print(f"正在連線至 Google Cloud Model ({model_name})...")
26
-
27
- # === 【學員填空練習點 3:初始化 Gemini Client】 ===
28
- # TODO: 呼叫 genai.Client 並傳入 api_key 參數來初始化 Client 物件,指派給 client 變數
29
- client = genai.Client(api_key=api_key)
30
-
31
- # === 【學員填空練習點 4:發送內容生成請求】 ===
32
- # TODO: 使用 client.models.generate_content 呼叫模型,傳入 model 參數與 contents 提示詞
33
- # 提示詞內容:請模型以繁體中文解釋什麼是『API 串接』,並用餐廳點餐作為比喻,字數約 100 字。
34
- response = client.models.generate_content(model=model_name, contents="请用中文解释为什么泡面很好吃,字数一百字以内")
35
- # === 【學員填空練習點 5:讀取並印出回應】 ===
36
- # TODO: 從 response 物件中取得 text 欄位內容,並列印出來。
37
- print("\n=== AI 回應結果 ===")
38
- # 請在下方實作列印邏輯:
39
- print(response.text)
40
- # for i in range(10):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
41
  # print(response.text)
 
1
+ # d2_1_gemini.py — Day2 學員填空
2
+ # cd examples/Day2/Student && python3 d2_1_gemini.py
3
+ # 參考答案:../Teacher/d2_1_gemini.py
4
+
5
+ import os
6
+ from google import genai
7
+
8
+ # === 【學員填空練習點 1:設置與載入 API Key】 ===
9
+ # TODO: 嘗試從環境變數 GEMINI_API_KEY 取得 API Key。如果未設定,則使用 input() 提示手動輸入。
10
+
11
+ def _telearn_require_gemini_api_key(*, allow_skip: bool = False) -> str:
12
+ """終端機輸入介面:請使用者自行貼上 GEMINI_API_KEY。"""
13
+ import os
14
+ key = (os.environ.get("GEMINI_API_KEY") or "").strip()
15
+ if key:
16
+ print("[Telearn] 已使用環境變數中的 GEMINI_API_KEY")
17
+ return key
18
+ print()
19
+ print("=" * 54)
20
+ print(" 本專案需要 Google Gemini API Key")
21
+ print(" 取得金鑰:https://aistudio.google.com/apikey")
22
+ print(" 請在下方貼上 Key 後按 Enter")
23
+ print(" (僅本次執行使用,不會寫入任何檔案)")
24
+ if allow_skip:
25
+ print(" 若要改用離線 Mock 模式:直接按 Enter 略過")
26
+ print("=" * 54)
27
+ try:
28
+ key = input("請輸入 GEMINI_API_KEY: ").strip()
29
+ except EOFError:
30
+ key = ""
31
+ if key:
32
+ os.environ["GEMINI_API_KEY"] = key
33
+ print("[Telearn] 已接收 API Key,開始連線…")
34
+ return key
35
+ if allow_skip:
36
+ print("[Telearn] 未輸入 Key → 使用離線 Mock / 略過雲端功能")
37
+ return ""
38
+ print("[結束] 未輸入 API Key,無法繼續執行 Gemini 功能。")
39
+ raise SystemExit(0)
40
+
41
+ api_key = _telearn_require_gemini_api_key(allow_skip=False)
42
+
43
+ # === 【學員填空練習點 2:設定模型名稱】 ===
44
+ # TODO: 設定使用的 Gemini 模型名稱(例如 'gemini-3.1-flash-lite')
45
+ model_name = "gemini-3.1-flash-lite"
46
+
47
+ print(f"正在連線至 Google Cloud Model ({model_name})...")
48
+
49
+ # === 【學員填空練習點 3:初始化 Gemini Client】 ===
50
+ # TODO: 呼叫 genai.Client 並傳入 api_key 參數來初始化 Client 物件,指派給 client 變數
51
+ client = genai.Client(api_key=api_key)
52
+
53
+ # === 【學員填空練習點 4:發送內容生成請求】 ===
54
+ # TODO: 使用 client.models.generate_content 呼叫模型,傳入 model 參數與 contents 提示詞
55
+ # 提示詞內容:請模型以繁體中文解釋什麼是『API 串接』,並用餐廳點餐作為比喻,字數約 100 字。
56
+ response = client.models.generate_content(model=model_name, contents="请用中文解释为什么泡面很好吃,字数一百字以内")
57
+ # === 【學員填空練習點 5:讀取並印出回應】 ===
58
+ # TODO: 從 response 物件中取得 text 欄位內容,並列印出來。
59
+ print("\n=== AI 回應結果 ===")
60
+ # 請在下方實作列印邏輯:
61
+ print(response.text)
62
+ # for i in range(10):
63
  # print(response.text)
projects/2026_AI營1/劉宗穎/ai_api串接練習/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Telearn Arcade Hub 入口檔(自動產生)
4
+ 實際執行: d2_1_gemini.py
5
+ 規格: projects/{班級}/{學生}/{專案}/main.py
6
+ """
7
+ from pathlib import Path
8
+ import os
9
+ import runpy
10
+ import sys
11
+
12
+ _ROOT = Path(__file__).resolve().parent
13
+ os.chdir(_ROOT)
14
+ if str(_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_ROOT))
16
+
17
+ # 執行原始學員腳本
18
+ runpy.run_path(str(_ROOT / 'd2_1_gemini.py'), run_name="__main__")
projects/2026_AI營1/劉宗穎/{mock_密室逃脱游戏 → mock_密室逃脫遊戲}/d2_3_chatbot_memory_game_template.py RENAMED
File without changes
projects/2026_AI營1/劉宗穎/mock_密室逃脫遊戲/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Telearn Arcade Hub 入口檔(自動產生)
4
+ 實際執行: d2_3_chatbot_memory_game_template.py
5
+ 規格: projects/{班級}/{學生}/{專案}/main.py
6
+ """
7
+ from pathlib import Path
8
+ import os
9
+ import runpy
10
+ import sys
11
+
12
+ _ROOT = Path(__file__).resolve().parent
13
+ os.chdir(_ROOT)
14
+ if str(_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_ROOT))
16
+
17
+ # 執行原始學員腳本
18
+ runpy.run_path(str(_ROOT / 'd2_3_chatbot_memory_game_template.py'), run_name="__main__")
projects/2026_AI營1/劉宗穎/{mock_关键词抓取 → mock_關鍵詞抓取}/d2_3_chatbot_memory_mock.py RENAMED
File without changes
projects/2026_AI營1/劉宗穎/mock_關鍵詞抓取/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Telearn Arcade Hub 入口檔(自動產生)
4
+ 實際執行: d2_3_chatbot_memory_mock.py
5
+ 規格: projects/{班級}/{學生}/{專案}/main.py
6
+ """
7
+ from pathlib import Path
8
+ import os
9
+ import runpy
10
+ import sys
11
+
12
+ _ROOT = Path(__file__).resolve().parent
13
+ os.chdir(_ROOT)
14
+ if str(_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_ROOT))
16
+
17
+ # 執行原始學員腳本
18
+ runpy.run_path(str(_ROOT / 'd2_3_chatbot_memory_mock.py'), run_name="__main__")
projects/2026_AI營1/劉宗穎/{mock&AI_api双模式 → mock與AI_api雙模式}/d2_3_chatbot_memory.py RENAMED
@@ -1,95 +1,124 @@
1
- # d2_3_chatbot_memory.py
2
- # Day 2: 生成式 AI 串接實戰 - 具對話記憶的智慧客服機器人 (Gemini / Local Mock 雙模)
3
-
4
- import os
5
- from google import genai
6
-
7
- # 優先讀取環境變數 GEMINI_MODEL,預設為 gemini-3.1-flash-lite
8
- GEMINI_MODEL = os.environ.get("GEMINI_MODEL", "gemini-3.1-flash-lite")
9
- api_key = os.environ.get(
10
- "GEMINI_API_KEY", "")
11
-
12
-
13
- def local_mock_response(user_input, history):
14
- user_input_lower = user_input.lower()
15
-
16
- # TODO: 處理 營業時間 / 你好 / 再見,其餘回兜底句
17
- if "營業時間" in user_input_lower or "時間" in user_input_lower:
18
- return "我們觀測站的營業時間為週一至週五的上午 9:00 至下午 6:00,週末休息。"
19
- # elif "XX" in user_input_lower or "XX" in user_input_lower:
20
- # return "XXXXXXXXXXXXXXXXX"
21
-
22
-
23
- else:
24
- turns = len(history) // 2 + 1
25
- return (
26
- f"(模擬模式)收到您的訊息:『{user_input}』。"
27
- f"這是我與您的第 {turns} 次對話,請問還有其他需要協助的嗎?"
28
- )
29
-
30
-
31
- def main():
32
- print("=" * 50)
33
- print(" 得能 AI 智慧客服對話系統 (支援對話上下文記憶)")
34
- print("=" * 50)
35
-
36
- use_mock = False
37
- chat_session = None
38
-
39
- if not api_key:
40
- print("[提示] 未檢測到環境變數 GEMINI_API_KEY,將自動啟用「離線模擬模式 (Local Mock Mode)」。")
41
- print(" (如需連線雲端模型,請設定 GEMINI_API_KEY 環境變數)")
42
- use_mock = True
43
- else:
44
- try:
45
- print(f"正在連線 Google Gemini 雲端客服伺服器 ({GEMINI_MODEL})...")
46
- client = genai.Client(api_key=api_key)
47
- # TODO: 設定系統指令 (System Instruction / System Prompt)
48
- # system_instruction = (
49
- # "你是一位專業的得能教育客服人員,語氣溫和客氣,"
50
- # "XXXXXXXXXXXXXXXXXXXXXXX"
51
- # )
52
-
53
- chat_session = client.chats.create(
54
- model=GEMINI_MODEL,
55
- config={'system_instruction': system_instruction}
56
- )
57
- print("雲端客服系統已就緒!")
58
- except Exception as e:
59
- print(f"連線雲端客服失敗 ({e}),切換至離線模擬模式...")
60
- use_mock = True
61
-
62
- local_history = []
63
-
64
- print("\n系統啟動完成!請輸入您要諮詢的問題(輸入 'quit' 或 'exit' 結束對話):")
65
- print("-" * 50)
66
-
67
- while True:
68
- try:
69
- user_input = input("\n[您]: ").strip()
70
- if not user_input:
71
- continue
72
-
73
- if user_input.lower() in ["quit", "exit"]:
74
- print("對話結束,感謝您的使用!")
75
- break
76
-
77
- if use_mock:
78
- ai_reply = local_mock_response(user_input, local_history)
79
- local_history.append({"role": "user", "text": user_input})
80
- local_history.append({"role": "model", "text": ai_reply})
81
- else:
82
- response = chat_session.send_message(user_input)
83
- ai_reply = response.text
84
-
85
- print(f"[AI 客服]: {ai_reply}")
86
-
87
- except KeyboardInterrupt:
88
- print("\n對話強行終止。再見!")
89
- break
90
- except Exception as e:
91
- print(f"\n[錯誤] 發生異常:{e}")
92
-
93
-
94
- if __name__ == "__main__":
95
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # d2_3_chatbot_memory.py
2
+ # Day 2: 生成式 AI 串接實戰 - 具對話記憶的智慧客服機器人 (Gemini / Local Mock 雙模)
3
+
4
+ import os
5
+ from google import genai
6
+
7
+ # 優先讀取環境變數 GEMINI_MODEL,預設為 gemini-3.1-flash-lite
8
+
9
+ def _telearn_require_gemini_api_key(*, allow_skip: bool = False) -> str:
10
+ """終端機輸入介面:請使用者自行貼上 GEMINI_API_KEY。"""
11
+ import os
12
+ key = (os.environ.get("GEMINI_API_KEY") or "").strip()
13
+ if key:
14
+ print("[Telearn] 已使用環境變數中的 GEMINI_API_KEY")
15
+ return key
16
+ print()
17
+ print("=" * 54)
18
+ print(" 本專案需要 Google Gemini API Key")
19
+ print(" 取得金鑰:https://aistudio.google.com/apikey")
20
+ print(" 請在下方貼上 Key 後按 Enter")
21
+ print(" (僅本次執行使用,不會寫入任何檔案)")
22
+ if allow_skip:
23
+ print(" 若要改用離線 Mock 模式:直接按 Enter 略過")
24
+ print("=" * 54)
25
+ try:
26
+ key = input("請輸入 GEMINI_API_KEY: ").strip()
27
+ except EOFError:
28
+ key = ""
29
+ if key:
30
+ os.environ["GEMINI_API_KEY"] = key
31
+ print("[Telearn] 已接收 API Key,開始連線…")
32
+ return key
33
+ if allow_skip:
34
+ print("[Telearn] 未輸入 Key → 使用離線 Mock / 略過雲端功能")
35
+ return ""
36
+ print("[結束] 未輸入 API Key,無法繼續執行 Gemini 功能。")
37
+ raise SystemExit(0)
38
+
39
+ GEMINI_MODEL = os.environ.get("GEMINI_MODEL", "gemini-3.1-flash-lite")
40
+ api_key = _telearn_require_gemini_api_key(allow_skip=True)
41
+
42
+
43
+ def local_mock_response(user_input, history):
44
+ user_input_lower = user_input.lower()
45
+
46
+ # TODO: 處理 營業時間 / 你好 / 再見,其餘回兜底句
47
+ if "營業時間" in user_input_lower or "時間" in user_input_lower:
48
+ return "我們觀測站的營業時間為週一至週五的上午 9:00 至下午 6:00,週末休息。"
49
+ # elif "XX" in user_input_lower or "XX" in user_input_lower:
50
+ # return "XXXXXXXXXXXXXXXXX"
51
+
52
+
53
+ else:
54
+ turns = len(history) // 2 + 1
55
+ return (
56
+ f"(模擬模式)收到您的訊息:『{user_input}』。"
57
+ f"這是我與您的第 {turns} 次對話,請問還有其他需要協助的嗎?"
58
+ )
59
+
60
+
61
+ def main():
62
+ print("=" * 50)
63
+ print(" 得能 AI 智慧客服對話系統 (支援對話上下文記憶)")
64
+ print("=" * 50)
65
+
66
+ use_mock = False
67
+ chat_session = None
68
+
69
+ if not api_key:
70
+ print("[提示] 未輸入 GEMINI_API_KEY,改用「離線模擬模式 (Local Mock Mode)」。")
71
+ use_mock = True
72
+ else:
73
+ try:
74
+ print(f"正在連線 Google Gemini 雲端客服伺服器 ({GEMINI_MODEL})...")
75
+ client = genai.Client(api_key=api_key)
76
+ # TODO: 設定系統指令 (System Instruction / System Prompt)
77
+ # system_instruction = (
78
+ # "你是一位專業的得能教育客服人員,語氣溫和客氣,"
79
+ # "XXXXXXXXXXXXXXXXXXXXXXX"
80
+ # )
81
+
82
+ chat_session = client.chats.create(
83
+ model=GEMINI_MODEL,
84
+ config={'system_instruction': system_instruction}
85
+ )
86
+ print("雲端客服系統已就緒!")
87
+ except Exception as e:
88
+ print(f"連線雲端客服失敗 ({e}),切換至離線模擬模式...")
89
+ use_mock = True
90
+
91
+ local_history = []
92
+
93
+ print("\n系統啟動完成!請輸入您要諮詢的問題(輸入 'quit' 或 'exit' 結束對話):")
94
+ print("-" * 50)
95
+
96
+ while True:
97
+ try:
98
+ user_input = input("\n[您]: ").strip()
99
+ if not user_input:
100
+ continue
101
+
102
+ if user_input.lower() in ["quit", "exit"]:
103
+ print("對話結束,感謝您的使用!")
104
+ break
105
+
106
+ if use_mock:
107
+ ai_reply = local_mock_response(user_input, local_history)
108
+ local_history.append({"role": "user", "text": user_input})
109
+ local_history.append({"role": "model", "text": ai_reply})
110
+ else:
111
+ response = chat_session.send_message(user_input)
112
+ ai_reply = response.text
113
+
114
+ print(f"[AI 客服]: {ai_reply}")
115
+
116
+ except KeyboardInterrupt:
117
+ print("\n對話強行終止。再見!")
118
+ break
119
+ except Exception as e:
120
+ print(f"\n[錯誤] 發生異常:{e}")
121
+
122
+
123
+ if __name__ == "__main__":
124
+ main()
projects/2026_AI營1/劉宗穎/{mock&AI_api双模式 → mock與AI_api雙模式}/d2_4_chatbot_memory.py RENAMED
@@ -1,129 +1,158 @@
1
- # d2_3_chatbot_memory.py
2
- # Day 2: 生成式 AI 串接實戰 - 具對話記憶的智慧客服機器人 (Gemini / Local Mock 雙模)
3
-
4
- import os
5
- from google import genai
6
-
7
- # 優先讀取環境變數 GEMINI_MODEL,預設為 gemini-3.1-flash-lite
8
- GEMINI_MODEL = os.environ.get("GEMINI_MODEL", "gemini-3.1-flash-lite")
9
- api_key = os.environ.get(
10
- "GEMINI_API_KEY", "AQ.Ab8RN6JvbUqLW-wcddeMducmta0aGE2QtcrmF4Kfd8nnpZ9mzg")
11
-
12
-
13
- def local_mock_response(user_input, history):
14
- user_input_lower = user_input.lower()
15
-
16
- if "營業時間" in user_input_lower or "時間" in user_input_lower:
17
- return "我们冰淇淋店的營業時間為週一至週五的上午 9:00 至下午 6:00,週末休息。"
18
- elif "地址" in user_input_lower or "地点" in user_input_lower or "地方" in user_input_lower:
19
- return "我们的店在求毛星球毛笔城市QQ镇喜喜街167号一楼。"
20
- elif "冰淇淋的口味" in user_input_lower or "口味" in user_input_lower:
21
- return "我们的冰淇淋的口味有草莓,芒果,巧克力,咖啡,蓝莓,汽水和芭乐。"
22
- elif "冰淇淋的价格" in user_input_lower or "价格" in user_input_lower or "冰淇淋怎么卖" in user_input_lower:
23
- return "一球100元,两球170元,三球250元,五球400元,十球750元。"
24
- elif "有打折吗" in user_input_lower or "有优惠吗" in user_input_lower or "有进行促销活动吗" in user_input_lower or "有活动吗" in user_input_lower or "折扣" in user_input_lower:
25
- return "我们有和吃鸡炸鸡店合作,去那里消费满1000元即可换一球冰淇淋。"
26
- elif "help" in user_input_lower or "帮助" in user_input_lower:
27
- return "\n輸入「營業時間、時間」即可知道營業時間\n輸入「地址、地點、地方」即可知道地址\n輸入「冰淇淋的口味、口味」即可知道口味\n輸入「冰淇淋的價格、價格、冰淇淋怎麼賣」即可知道價格\n輸入「有打折嗎、有優惠嗎、有進行促銷活動嗎、有活動嗎、折扣」即可知道優惠資訊\n輸入「停車場、停車、附近有沒有停車場」即可知道停車資訊\n輸入「可以客製化嗎、客製化的服務、客製化」即可知道客製化服務\n輸入「客製化的價錢、客製化要多少錢」即可知道客製化價格\n輸入「推薦、招牌」即可知道推薦口味\n輸入「外帶、內用」即可知道用餐方式\n輸入「付款、支付、可以刷卡」即可知道付款方式。"
28
- elif "你好" in user_input_lower or "hello" in user_input_lower or "hi" in user_input_lower or "Hi" in user_input_lower:
29
- return "你好!请问您需要什么帮助吗?"
30
- elif "停车场" in user_input_lower or "停车" in user_input_lower or "附近有没有停车场" in user_input_lower:
31
- return "我们的店附近有三个停车场,步行三分钟即可到达我们的店。"
32
- elif "可以克制化吗" in user_input_lower or "客制化的服务" in user_input_lower or "客制化" in user_input_lower:
33
- return "我们的店可以克制化冰淇淋,可以选择彩糖、珍珠、草莓酱、巧克力酱、布丁、奶油和炼乳。"
34
- elif "客制化的价钱" in user_input_lower or "客制化要多少钱" in user_input_lower or "客制化要多少钱?" in user_input_lower:
35
- return "客制化每一种材料都要再加15元。"
36
- elif "推薦" in user_input_lower or "招牌" in user_input_lower:
37
- return "推薦您試試草莓、巧克力和芭樂口味,都是熱門選擇。"
38
- elif "外帶" in user_input_lower or "內用" in user_input_lower:
39
- return "我們提供內用及外帶服務。"
40
- elif "付款" in user_input_lower or "支付" in user_input_lower or "可以刷卡" in user_input_lower:
41
- return "我們接受現金、信用卡及行動支付。"
42
- elif "谢谢" in user_input_lower or "Thank you" in user_input_lower:
43
- return "不客气!还需要什么帮助吗?"
44
-
45
- else:
46
- # 計算目前是第幾輪對話(一輪包含使用者輸入與客服回覆,共 2 個元素)
47
- # Why: 藉由傳入的 history 串列長度除以 2,即可動態得知目前互動的輪數,模擬記憶狀態。
48
- turns = len(history) // 2 + 1
49
- return (
50
- f"(模擬模式)收到您的訊息:『{user_input}』。"
51
- f"這是我與您的第 {turns} 次對話,請問還有其他需要協助的嗎?"
52
- )
53
-
54
-
55
- def main():
56
- print("=" * 50)
57
- print(" 吃吃吃冰淇淋店客服對話系統 (支援對話上下文記憶)")
58
- print("=" * 50)
59
-
60
- use_mock = False
61
- chat_session = None
62
-
63
- if not api_key:
64
- print("[提示] 未檢測到環境變數 GEMINI_API_KEY,將自動啟用「離線模擬模式 (Local Mock Mode)」。")
65
- print(" (如需連線雲端模型,請設定 GEMINI_API_KEY 環境變數)")
66
- use_mock = True
67
- else:
68
- try:
69
- print(f"正在連線 Google Gemini 雲端客服伺服器 ({GEMINI_MODEL})...")
70
- client = genai.Client(api_key=api_key)
71
- # TODO: 設定系統指令 (System Instruction / System Prompt)
72
- system_instruction = (
73
- "你是一位冰淇淋店的客服,负责提供资讯"
74
- "我们冰淇淋店的營業時間為週一至週五的上午 9:00 至下午 6:00,週末休息。"
75
- "我们的店在求毛星球毛笔城市QQ镇喜喜街167号一楼。"
76
- "冰淇淋的价格一球100元,两球170元,三球250元,五球400元,十球750元。"
77
- "我们的店有进行优惠,有和吃鸡炸鸡店合作,去那里消费满1000元即可换一球冰淇淋。"
78
- "这是我们的资讯:輸入「營業時間、時間」即可知道營業時間,輸入「地址、地點、地方」即可知道地址,輸入「冰淇淋的口味、口味」即可知道口味,輸入「冰淇淋的價格、價格、冰淇淋怎麼賣」即可知道價格,輸入「有打折嗎、有優惠嗎、有進行促銷活動嗎、有活動嗎、折扣」即可知道優惠資訊,輸入「停車場、停車、附近有沒有停車場」即可知道停車資訊,輸入「可以客製化嗎、客製化的服務、客製化」即可知道客製化服務,輸入「客製化的價錢、客製化要多少錢」即可知道客製化價格,輸入「推薦、招牌」即可知道推薦口味,輸入「外帶、內用」即可知道用餐方式,輸入「付款、支付、可以刷卡」即可知道付款方式。"
79
- "你应该亲切的向客户问好。"
80
- "我们的店附近有三个停车场,步行三分钟即可到达我们的店。"
81
- "我们的店可以克制化冰淇淋,可以选择彩糖、珍珠、草莓酱、巧克力酱、布丁、奶油和炼乳。"
82
- "客制化每一种材料都要再加15元。"
83
- "我們提供內用及外帶服務。"
84
- "我們接受現金、信用卡及行動支付。"
85
- )
86
-
87
- chat_session = client.chats.create(
88
- model=GEMINI_MODEL,
89
- config={'system_instruction': system_instruction}
90
- )
91
- print("雲端客服系統已就緒!")
92
- except Exception as e:
93
- print(f"連線雲端客服失敗 ({e}),切換至離線模擬模式...")
94
- use_mock = True
95
-
96
- local_history = []
97
-
98
- print("\n系統啟動完成!請輸入您要諮詢的問題(輸入 'quit' 或 'exit' 結束對話):")
99
- print("-" * 50)
100
-
101
- while True:
102
- try:
103
- user_input = input("\n[您]: ").strip()
104
- if not user_input:
105
- continue
106
-
107
- if user_input.lower() in ["quit", "exit"]:
108
- print("對話結束,感謝您的使用!")
109
- break
110
-
111
- if use_mock:
112
- ai_reply = local_mock_response(user_input, local_history)
113
- local_history.append({"role": "user", "text": user_input})
114
- local_history.append({"role": "model", "text": ai_reply})
115
- else:
116
- response = chat_session.send_message(user_input)
117
- ai_reply = response.text
118
-
119
- print(f"[AI 客服]: {ai_reply}")
120
-
121
- except KeyboardInterrupt:
122
- print("\n對話強行終止。再見!")
123
- break
124
- except Exception as e:
125
- print(f"\n[錯誤] 發生異常:{e}")
126
-
127
-
128
- if __name__ == "__main__":
129
- main()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # d2_3_chatbot_memory.py
2
+ # Day 2: 生成式 AI 串接實戰 - 具對話記憶的智慧客服機器人 (Gemini / Local Mock 雙模)
3
+
4
+ import os
5
+ from google import genai
6
+
7
+ # 優先讀取環境變數 GEMINI_MODEL,預設為 gemini-3.1-flash-lite
8
+
9
+ def _telearn_require_gemini_api_key(*, allow_skip: bool = False) -> str:
10
+ """終端機輸入介面:請使用者自行貼上 GEMINI_API_KEY。"""
11
+ import os
12
+ key = (os.environ.get("GEMINI_API_KEY") or "").strip()
13
+ if key:
14
+ print("[Telearn] 已使用環境變數中的 GEMINI_API_KEY")
15
+ return key
16
+ print()
17
+ print("=" * 54)
18
+ print(" 本專案需要 Google Gemini API Key")
19
+ print(" 取得金鑰:https://aistudio.google.com/apikey")
20
+ print(" 請在下方貼上 Key 後按 Enter")
21
+ print(" (僅本次執行使用,不會寫入任何檔案)")
22
+ if allow_skip:
23
+ print(" 若要改用離線 Mock 模式:直接按 Enter 略過")
24
+ print("=" * 54)
25
+ try:
26
+ key = input("請輸入 GEMINI_API_KEY: ").strip()
27
+ except EOFError:
28
+ key = ""
29
+ if key:
30
+ os.environ["GEMINI_API_KEY"] = key
31
+ print("[Telearn] 已接收 API Key,開始連線…")
32
+ return key
33
+ if allow_skip:
34
+ print("[Telearn] 未輸入 Key → 使用離線 Mock / 略過雲端功能")
35
+ return ""
36
+ print("[結束] 未輸入 API Key,無法繼續執行 Gemini 功能。")
37
+ raise SystemExit(0)
38
+
39
+ GEMINI_MODEL = os.environ.get("GEMINI_MODEL", "gemini-3.1-flash-lite")
40
+ api_key = _telearn_require_gemini_api_key(allow_skip=True)
41
+
42
+
43
+ def local_mock_response(user_input, history):
44
+ user_input_lower = user_input.lower()
45
+
46
+ if "營業時間" in user_input_lower or "時間" in user_input_lower:
47
+ return "我们冰淇淋店的營業時間為週一至週五的上午 9:00 至下午 6:00,週末休息。"
48
+ elif "地址" in user_input_lower or "地点" in user_input_lower or "地方" in user_input_lower:
49
+ return "我们的店在求毛星球毛笔城市QQ镇喜喜街167号一楼。"
50
+ elif "冰淇淋的口味" in user_input_lower or "口味" in user_input_lower:
51
+ return "我们的冰淇淋的口味有草莓,芒果,巧克力,咖啡,蓝莓,汽水和芭乐。"
52
+ elif "冰淇淋的价格" in user_input_lower or "价格" in user_input_lower or "冰淇淋怎么卖" in user_input_lower:
53
+ return "一球100元,两球170元,三球250元,五球400元,十球750元。"
54
+ elif "有打折吗" in user_input_lower or "有优惠吗" in user_input_lower or "有进行促销活动吗" in user_input_lower or "有活动吗" in user_input_lower or "折扣" in user_input_lower:
55
+ return "我们有和吃鸡炸鸡店合作,去那里消费满1000元即可换一球冰淇淋。"
56
+ elif "help" in user_input_lower or "帮助" in user_input_lower:
57
+ return "\n輸入「營業時間、時間」即可知道營業時間\n輸入「地址、地點、地方」即可知道地址\n輸入「冰淇淋的口味、口味」即可知道口味\n輸入「冰淇淋的價格、價格、冰淇淋怎麼賣」即可知道價格\n輸入「有打折嗎、有優惠嗎、有進行促銷活動嗎、有活動嗎、折扣」即可知道優惠資訊\n輸入「停車場、停車、附近有沒有停車場」即可知道停車資訊\n輸入「可以客製化嗎、客製化的服務、客製化」即可知道客製化服務\n輸入「客製化的價錢、客製化要多少錢」即可知道客製化價格\n輸入「推薦、招牌」即可知道推薦口味\n輸入「外帶、內用」即可知道用餐方式\n輸入「付款、支付、可以刷卡」即可知道付款方式。"
58
+ elif "你好" in user_input_lower or "hello" in user_input_lower or "hi" in user_input_lower or "Hi" in user_input_lower:
59
+ return "你好!请问您需要什么帮助吗?"
60
+ elif "停车场" in user_input_lower or "停车" in user_input_lower or "附近有没有停车场" in user_input_lower:
61
+ return "我们的店附近有三个停车场,步行三分钟即可到达我们的店。"
62
+ elif "可以克制化吗" in user_input_lower or "客制化的服务" in user_input_lower or "客制化" in user_input_lower:
63
+ return "我们的店可以克制化冰淇淋,可以选择彩糖、珍珠、草莓酱、巧克力酱、布丁、奶油和炼乳。"
64
+ elif "客制化的价钱" in user_input_lower or "客制化要多少钱" in user_input_lower or "客制化要多少钱?" in user_input_lower:
65
+ return "客制化每一种材料都要再加15元。"
66
+ elif "推薦" in user_input_lower or "招牌" in user_input_lower:
67
+ return "推薦您試試草莓、巧克力和芭樂口味,都是熱門選擇。"
68
+ elif "外帶" in user_input_lower or "內用" in user_input_lower:
69
+ return "我們提供內用及外帶服務。"
70
+ elif "付款" in user_input_lower or "支付" in user_input_lower or "可以刷卡" in user_input_lower:
71
+ return "我們接受現金、信用卡及行動支付。"
72
+ elif "谢谢" in user_input_lower or "Thank you" in user_input_lower:
73
+ return "不客气!还需要什么帮助吗?"
74
+
75
+ else:
76
+ # 計算目前是第幾輪對話(一輪包含使用者輸入與客服回覆,共 2 個元素)
77
+ # Why: 藉由傳入的 history 串列長度除以 2,即可動態得知目前互動的輪數,模擬記憶狀態。
78
+ turns = len(history) // 2 + 1
79
+ return (
80
+ f"(模擬模式)收到您的訊息:『{user_input}』。"
81
+ f"這是我與您的第 {turns} 次對話,請問還有其他需要協助的嗎?"
82
+ )
83
+
84
+
85
+ def main():
86
+ print("=" * 50)
87
+ print(" 吃吃吃冰淇淋店客服對話系統 (支援對話上下文記憶)")
88
+ print("=" * 50)
89
+
90
+ use_mock = False
91
+ chat_session = None
92
+
93
+ if not api_key:
94
+ print("[提示] 未輸入 GEMINI_API_KEY,改用「離線模擬模式 (Local Mock Mode)」。")
95
+ use_mock = True
96
+ else:
97
+ try:
98
+ print(f"正在連線 Google Gemini 雲端客服伺服器 ({GEMINI_MODEL})...")
99
+ client = genai.Client(api_key=api_key)
100
+ # TODO: 設定系統指令 (System Instruction / System Prompt)
101
+ system_instruction = (
102
+ "你是一位冰淇淋店的客服,负责提供资讯"
103
+ "我们冰淇淋店的營業時間為週一至週五的上午 9:00 至下午 6:00,週末休息。"
104
+ "我们的店在求毛星球毛笔城市QQ镇喜喜街167号一楼。"
105
+ "冰淇淋的��格一球100元,两球170元,三球250元,五球400元,十球750元。"
106
+ "我们的店有进行优惠,有和吃鸡炸鸡店合作,去那里消费满1000元即可换一球冰淇淋。"
107
+ "这是我们的资讯:輸入「營業時間、時間」即可知道營業時間,輸入「地址、地點、地方」即可知道地址,輸入「冰淇淋的口味、口味」即可知道口味,輸入「冰淇淋的價格、價格、冰淇淋怎麼賣」即可知道價格,輸入「有打折嗎、有優惠嗎、有進行促銷活動嗎、有活動嗎、折扣」即可知道優惠資訊,輸入「停車場、停車、附近有沒有停車場」即可知道停車資訊,輸入「可以客製化嗎、客製化的服務、客製化」即可知道客製化服務,輸入「客製化的價錢、客製化要多少錢」即可知道客製化價格,輸入「推薦、招牌」即可知道推薦口味,輸入「外帶、內用」即可知道用餐方式,輸入「付款、支付、可以刷卡」即可知道付款方式。"
108
+ "你应该亲切的向客户问好。"
109
+ "我们的店附近有三个停车场,步行三分钟即可到达我们的店。"
110
+ "我们的店可以克制化冰淇淋,可以选择彩糖、珍珠、草莓酱、巧克力酱、布丁、奶油和炼乳。"
111
+ "客制化每一种材料都要再加15元。"
112
+ "我們提供內用及外帶服務。"
113
+ "我們接受現金、信用卡及行動支付。"
114
+ )
115
+
116
+ chat_session = client.chats.create(
117
+ model=GEMINI_MODEL,
118
+ config={'system_instruction': system_instruction}
119
+ )
120
+ print("雲端客服系統已就緒!")
121
+ except Exception as e:
122
+ print(f"連線雲端客服失敗 ({e}),切換至離線模擬模式...")
123
+ use_mock = True
124
+
125
+ local_history = []
126
+
127
+ print("\n系統啟動完成!請輸入您要諮詢的問題(輸入 'quit' 或 'exit' 結束對話):")
128
+ print("-" * 50)
129
+
130
+ while True:
131
+ try:
132
+ user_input = input("\n[您]: ").strip()
133
+ if not user_input:
134
+ continue
135
+
136
+ if user_input.lower() in ["quit", "exit"]:
137
+ print("對話結束,感謝您的使用!")
138
+ break
139
+
140
+ if use_mock:
141
+ ai_reply = local_mock_response(user_input, local_history)
142
+ local_history.append({"role": "user", "text": user_input})
143
+ local_history.append({"role": "model", "text": ai_reply})
144
+ else:
145
+ response = chat_session.send_message(user_input)
146
+ ai_reply = response.text
147
+
148
+ print(f"[AI 客服]: {ai_reply}")
149
+
150
+ except KeyboardInterrupt:
151
+ print("\n對話強行終止。再見!")
152
+ break
153
+ except Exception as e:
154
+ print(f"\n[錯誤] 發生異常:{e}")
155
+
156
+
157
+ if __name__ == "__main__":
158
+ main()
projects/2026_AI營1/劉宗穎/mock與AI_api雙模式/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Telearn Arcade Hub 入口檔(自動產生)
4
+ 實際執行: d2_4_chatbot_memory.py
5
+ 規格: projects/{班級}/{學生}/{專案}/main.py
6
+ """
7
+ from pathlib import Path
8
+ import os
9
+ import runpy
10
+ import sys
11
+
12
+ _ROOT = Path(__file__).resolve().parent
13
+ os.chdir(_ROOT)
14
+ if str(_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_ROOT))
16
+
17
+ # 執行原始學員腳本
18
+ runpy.run_path(str(_ROOT / 'd2_4_chatbot_memory.py'), run_name="__main__")
projects/2026_AI營1/劉宗穎/{三指捏游戏 → 三指捏遊戲}/d5_pinch_challenge_solution.py RENAMED
@@ -1,217 +1,216 @@
1
- """
2
- Week 10 - 魔法手勢:Pinch 偵測與手勢按鈕 (三指捏合挑戰 Template 填空版)
3
- ==================================================================
4
- 本週挑戰目標:
5
- 1. 實作「三指捏合 (Three-Finger Pinch)」:
6
- 必須同時滿足「大拇指 (4) 與食指 (8) 的距離 < Threshold」
7
- 以及「大拇指 (4) 與中指 (12) 的距離 < Threshold」才判定為 Pinch
8
- 2. 用三指碰觸 Pygame 畫面上的按鈕 + 捏合 = 魔法點擊
9
- 3. 使用邊緣觸發 (Edge Detection) 防止重複觸發
10
- 4. 新增 30 秒限時機制,時間到顯示最終得分
11
- """
12
- import pygame
13
- import cv2
14
- import mediapipe as mp
15
- import math
16
- import sys
17
- import os
18
- import random
19
-
20
- # ============================================
21
- # 1. 初始化
22
- # ============================================
23
- pygame.init()
24
- WIDTH, HEIGHT = 800, 600
25
- screen = pygame.display.set_mode((WIDTH, HEIGHT))
26
- pygame.display.set_caption("Week 10 - 三指魔法手勢按鈕 (填空版) ")
27
- clock = pygame.time.Clock()
28
- from mediapipe.tasks import python
29
- from mediapipe.tasks.python import vision
30
-
31
- # 攝影機
32
- cap = cv2.VideoCapture(0)
33
-
34
- # 【TODO 1】初始化 MediaPipe 手勢偵測器 (Hand Landmarker)
35
- \
36
-
37
- try:
38
- from mediapipe.tasks import python
39
- from mediapipe.tasks.python import vision
40
- base_options = python.BaseOptions(model_asset_path=os.path.join('hand_landmarker.task'))
41
- options = vision.HandLandmarkerOptions(base_options=base_options, num_hands=1)
42
- detector = vision.HandLandmarker.create_from_options(options)
43
- except Exception as e:
44
- print(f"Error loading model: {e}")
45
- pygame.quit()
46
- sys.exit()
47
-
48
- # ============================================
49
- # 2. 遊戲變數
50
- # ============================================
51
- # 手指座標
52
- finger_x, finger_y = 400, 300 # 食指
53
- thumb_x, thumb_y = 400, 300 # 大拇指
54
- middle_x, middle_y = 400, 300 # 中指
55
-
56
- # 距離變數
57
- dist_thumb_index = 0.0
58
- dist_thumb_middle = 0.0
59
-
60
- # Pinch 相關變數
61
- is_pinching = False
62
- was_pinching = False # 邊緣觸發
63
- PINCH_THRESHOLD = 50 # 距離臨界值
64
-
65
- # 按鈕設定
66
- button_rect = pygame.Rect(300, 250, 200, 100)
67
- button_colors = [(100, 100, 220), (220, 100, 100),
68
- (100, 220, 100), (220, 180, 50)]
69
- current_color_index = 0
70
-
71
- # 點擊計數器
72
- click_count = 0
73
-
74
- # --- 限時機制變數 ---
75
- START_TIME = pygame.time.get_ticks()
76
- GAME_DURATION = 30 # 遊戲限制時間(秒)
77
- game_state = "PLAYING" # 狀態機
78
-
79
- # ============================================
80
- # 3. 主迴圈
81
- # ============================================
82
- running = True
83
- while running:
84
- for event in pygame.event.get():
85
- if event.type == pygame.QUIT:
86
- running = False
87
-
88
- # --- 讀取攝影機與手勢偵測 ---
89
- success, img = cap.read()
90
- is_pinching = False # 每幀重設狀態,防止手離屏時卡死
91
-
92
- if success:
93
- img = cv2.flip(img, 1) # 鏡像翻轉
94
- img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
95
-
96
- mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=img_rgb)
97
- results = detector.detect(mp_image)
98
-
99
- # === 【TODO 2:挑戰關卡三之手部安全偵測】 ===
100
- # 1. 判斷手部是否在畫面中 (results.hand_landmarks 是否有資料)
101
- # 2. 如果在畫面中,取得食指 (8)、大拇指 (4)、中指 (12) 的像素座標
102
- # 3. 如果手不在畫面中,將 is_pinching 設為 False
103
- #
104
- hand_in_frame = False
105
- if results.hand_landmarks:
106
- hand_in_frame = True
107
- for hand_lms in results.hand_landmarks:
108
- # 食指 (8)
109
- lm8 = hand_lms[8]
110
- finger_x = int(lm8.x * WIDTH)
111
- finger_y = int(lm8.y * HEIGHT)
112
-
113
- # 大拇指 (4)
114
- lm4 = hand_lms[4]
115
- thumb_x = int(lm4.x * WIDTH)
116
- thumb_y = int(lm4.y * HEIGHT)
117
-
118
- # 中指 (12)
119
- lm12 = hand_lms[12]
120
- middle_x = int(lm12.x * WIDTH)
121
- middle_y = int(lm12.y * HEIGHT)
122
-
123
- # === 【TODO 3:挑戰關卡一之三指捏合判定】 ===
124
- # 提示:計算食指與大拇指距離,以及中指與大拇指距離
125
- # 兩組距離皆小於 PINCH_THRESHOLD 時,is_pinching 才為 True
126
- dist_thumb_index = math.hypot(finger_x - thumb_x, finger_y - thumb_y)
127
- dist_thumb_middle = math.hypot(middle_x - thumb_x, middle_y - thumb_y)
128
- is_pinching = (dist_thumb_index < PINCH_THRESHOLD) and (dist_thumb_middle < PINCH_THRESHOLD)
129
-
130
- dist_thumb_index = math.hypot(finger_x - thumb_x, finger_y - thumb_y)
131
- dist_thumb_middle = math.hypot(middle_x - thumb_x, middle_y - thumb_y)
132
- is_pinching = (dist_thumb_index < PINCH_THRESHOLD) and (dist_thumb_middle < PINCH_THRESHOLD)
133
-
134
- # --- 狀態機邏輯處理 ---
135
- if game_state == "PLAYING":
136
- # 1. 計算剩餘時間
137
- current_ticks = pygame.time.get_ticks()
138
- elapsed_seconds = (current_ticks - START_TIME) / 1000
139
- remaining_time = max(0, GAME_DURATION - elapsed_seconds)
140
-
141
- # 2. 檢查時間是否到了
142
- if remaining_time <= 0:
143
- game_state = "GAME_OVER"
144
-
145
- # 3. 邊緣觸發點擊判斷
146
- if is_pinching and not was_pinching:
147
- finger_rect = pygame.Rect(finger_x - 5, finger_y - 5, 10, 10)
148
- if finger_rect.colliderect(button_rect):
149
- click_count += 1
150
- current_color_index = (current_color_index + 1) % len(button_colors)
151
-
152
- # === 【TODO 4:挑戰關卡二之按鈕隨機移動】 ===
153
- # 提示:當點擊發生時,讓按鈕的 x 和 y 座標隨機分配在畫面內
154
-
155
- button_rect.x = random.randint(0, WIDTH - button_rect.width)
156
- button_rect.y = random.randint(0, HEIGHT - button_rect.height)
157
- print(f" 魔法三指點擊!第 {click_count} 次")
158
-
159
- # 更新上一幀狀態
160
- was_pinching = is_pinching
161
-
162
- # --- 繪製畫面 ---
163
- screen.fill((20, 20, 35))
164
- font = pygame.font.SysFont(None, 32)
165
- large_font = pygame.font.SysFont(None, 64)
166
-
167
- if game_state == "PLAYING":
168
- # 繪製按鈕
169
- btn_color = button_colors[current_color_index]
170
- pygame.draw.rect(screen, btn_color, button_rect, border_radius=16)
171
-
172
- btn_text = font.render(f"Clicks: {click_count}", True, (255, 255, 255))
173
- screen.blit(btn_text, (button_rect.centerx - btn_text.get_width() // 2,
174
- button_rect.centery - btn_text.get_height() // 2))
175
-
176
- # 顯示倒數計時
177
- time_text = font.render(f"Time Left: {remaining_time:.1f}s", True, (255, 220, 100))
178
- screen.blit(time_text, (WIDTH - time_text.get_width() - 20, 10))
179
-
180
- # 畫手指游標
181
- cursor_color = (50, 255, 50) if is_pinching else (255, 50, 50)
182
- pygame.draw.circle(screen, cursor_color, (finger_x, finger_y), 12) # 食指
183
- pygame.draw.circle(screen, (255, 255, 255), (thumb_x, thumb_y), 12) # 大拇指
184
- pygame.draw.circle(screen, (100, 150, 255), (middle_x, middle_y), 12) # 中指
185
-
186
- # === 【TODO 5:挑戰關卡三之畫面提示】 ===
187
- # 提示:當手不在畫面中時,在畫面上方顯示警告文字提醒學生將手部放回
188
-
189
- if not hand_in_frame:
190
- warning_text = font.render("OUT OF FRAME - PLEASE PUT HANDS BACK", True, (255, 100, 100))
191
- screen.blit(warning_text, (WIDTH // 2 - warning_text.get_width() // 2, 100))
192
-
193
- # 顯示調試數值
194
- small_font = pygame.font.SysFont(None, 24)
195
- status = small_font.render(f"Finger: ({finger_x}, {finger_y})", True, (150, 150, 150))
196
- screen.blit(status, (10, 10))
197
-
198
- dist_text = small_font.render(f"Dist Thumb-Index: {dist_thumb_index:.0f} px", True, (150, 150, 200))
199
- screen.blit(dist_text, (10, 35))
200
-
201
- dist_text2 = small_font.render(f"Dist Thumb-Middle: {dist_thumb_middle:.0f} px", True, (150, 150, 200))
202
- screen.blit(dist_text2, (10, 60))
203
-
204
- elif game_state == "GAME_OVER":
205
- # 繪製遊戲結束畫面
206
- over_text = large_font.render("GAME OVER", True, (255, 50, 50))
207
- score_text = font.render(f"Total Clicks: {click_count} times", True, (255, 255, 255))
208
-
209
- screen.blit(over_text, (WIDTH // 2 - over_text.get_width() // 2, HEIGHT // 2 - 50))
210
- screen.blit(score_text, (WIDTH // 2 - score_text.get_width() // 2, HEIGHT // 2 + 20))
211
-
212
- pygame.display.update()
213
- clock.tick(30)
214
-
215
- cap.release()
216
- pygame.quit()
217
- sys.exit()
 
1
+ """
2
+ Week 10 - 魔法手勢:Pinch 偵測與手勢按鈕 (三指捏合挑戰 Template 填空版)
3
+ ==================================================================
4
+ 本週挑戰目標:
5
+ 1. 實作「三指捏合 (Three-Finger Pinch)」:
6
+ 必須同時滿足「大拇指 (4) 與食指 (8) 的距離 < Threshold」
7
+ 以及「大拇指 (4) 與中指 (12) 的距離 < Threshold」才判定為 Pinch
8
+ 2. 用三指碰觸 Pygame 畫面上的按鈕 + 捏合 = 魔法點擊
9
+ 3. 使用邊緣觸發 (Edge Detection) 防止重複觸發
10
+ 4. 新增 30 秒限時機制,時間到顯示最終得分
11
+ """
12
+ import pygame
13
+ import cv2
14
+ import mediapipe as mp
15
+ import math
16
+ import sys
17
+ import os
18
+ import random
19
+
20
+ # ============================================
21
+ # 1. 初始化
22
+ # ============================================
23
+ pygame.init()
24
+ WIDTH, HEIGHT = 800, 600
25
+ screen = pygame.display.set_mode((WIDTH, HEIGHT))
26
+ pygame.display.set_caption("Week 10 - 三指魔法手勢按鈕 (填空版) ")
27
+ clock = pygame.time.Clock()
28
+ from mediapipe.tasks import python
29
+ from mediapipe.tasks.python import vision
30
+
31
+ # 攝影機
32
+ cap = cv2.VideoCapture(0)
33
+
34
+ # 【TODO 1】初始�� MediaPipe 手勢偵測器 (Hand Landmarker)
35
+
36
+ try:
37
+ from mediapipe.tasks import python
38
+ from mediapipe.tasks.python import vision
39
+ base_options = python.BaseOptions(model_asset_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'hand_landmarker.task'))
40
+ options = vision.HandLandmarkerOptions(base_options=base_options, num_hands=1)
41
+ detector = vision.HandLandmarker.create_from_options(options)
42
+ except Exception as e:
43
+ print(f"Error loading model: {e}")
44
+ pygame.quit()
45
+ sys.exit()
46
+
47
+ # ============================================
48
+ # 2. 遊戲變數
49
+ # ============================================
50
+ # 手指座標
51
+ finger_x, finger_y = 400, 300 # 食指
52
+ thumb_x, thumb_y = 400, 300 # 大拇指
53
+ middle_x, middle_y = 400, 300 # 中指
54
+
55
+ # 距離變數
56
+ dist_thumb_index = 0.0
57
+ dist_thumb_middle = 0.0
58
+
59
+ # Pinch 相關變數
60
+ is_pinching = False
61
+ was_pinching = False # 邊緣觸發
62
+ PINCH_THRESHOLD = 50 # 距離臨界值
63
+
64
+ # 按鈕設定
65
+ button_rect = pygame.Rect(300, 250, 200, 100)
66
+ button_colors = [(100, 100, 220), (220, 100, 100),
67
+ (100, 220, 100), (220, 180, 50)]
68
+ current_color_index = 0
69
+
70
+ # 點擊計數器
71
+ click_count = 0
72
+
73
+ # --- 限時機制變數 ---
74
+ START_TIME = pygame.time.get_ticks()
75
+ GAME_DURATION = 30 # 遊戲限制時間(秒)
76
+ game_state = "PLAYING" # 狀態機
77
+
78
+ # ============================================
79
+ # 3. 主迴圈
80
+ # ============================================
81
+ running = True
82
+ while running:
83
+ for event in pygame.event.get():
84
+ if event.type == pygame.QUIT:
85
+ running = False
86
+
87
+ # --- 讀取攝影機與手勢偵測 ---
88
+ success, img = cap.read()
89
+ is_pinching = False # 每幀重設狀態,防止手離屏時卡死
90
+
91
+ if success:
92
+ img = cv2.flip(img, 1) # 鏡像翻轉
93
+ img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
94
+
95
+ mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=img_rgb)
96
+ results = detector.detect(mp_image)
97
+
98
+ # === 【TODO 2:挑戰關卡三之手部安全偵測】 ===
99
+ # 1. 判斷手部是否在畫面中 (results.hand_landmarks 是否有資料)
100
+ # 2. 如果在畫面中,取得食指 (8)、大拇指 (4)、中指 (12) 的像素座標
101
+ # 3. 如果手不在畫面中,將 is_pinching 設為 False
102
+ #
103
+ hand_in_frame = False
104
+ if results.hand_landmarks:
105
+ hand_in_frame = True
106
+ for hand_lms in results.hand_landmarks:
107
+ # 食指 (8)
108
+ lm8 = hand_lms[8]
109
+ finger_x = int(lm8.x * WIDTH)
110
+ finger_y = int(lm8.y * HEIGHT)
111
+
112
+ # 大拇指 (4)
113
+ lm4 = hand_lms[4]
114
+ thumb_x = int(lm4.x * WIDTH)
115
+ thumb_y = int(lm4.y * HEIGHT)
116
+
117
+ # 中指 (12)
118
+ lm12 = hand_lms[12]
119
+ middle_x = int(lm12.x * WIDTH)
120
+ middle_y = int(lm12.y * HEIGHT)
121
+
122
+ # === 【TODO 3:挑戰關卡一之三指捏合判定】 ===
123
+ # 提示:計算食指與大拇指距離,以及中指與大拇指距離
124
+ # 兩組距離皆小於 PINCH_THRESHOLD 時,is_pinching 才為 True
125
+ dist_thumb_index = math.hypot(finger_x - thumb_x, finger_y - thumb_y)
126
+ dist_thumb_middle = math.hypot(middle_x - thumb_x, middle_y - thumb_y)
127
+ is_pinching = (dist_thumb_index < PINCH_THRESHOLD) and (dist_thumb_middle < PINCH_THRESHOLD)
128
+
129
+ dist_thumb_index = math.hypot(finger_x - thumb_x, finger_y - thumb_y)
130
+ dist_thumb_middle = math.hypot(middle_x - thumb_x, middle_y - thumb_y)
131
+ is_pinching = (dist_thumb_index < PINCH_THRESHOLD) and (dist_thumb_middle < PINCH_THRESHOLD)
132
+
133
+ # --- 狀態機邏輯處理 ---
134
+ if game_state == "PLAYING":
135
+ # 1. 計算剩餘時間
136
+ current_ticks = pygame.time.get_ticks()
137
+ elapsed_seconds = (current_ticks - START_TIME) / 1000
138
+ remaining_time = max(0, GAME_DURATION - elapsed_seconds)
139
+
140
+ # 2. 檢查時間是否到了
141
+ if remaining_time <= 0:
142
+ game_state = "GAME_OVER"
143
+
144
+ # 3. 邊緣觸發點擊判斷
145
+ if is_pinching and not was_pinching:
146
+ finger_rect = pygame.Rect(finger_x - 5, finger_y - 5, 10, 10)
147
+ if finger_rect.colliderect(button_rect):
148
+ click_count += 1
149
+ current_color_index = (current_color_index + 1) % len(button_colors)
150
+
151
+ # === 【TODO 4:挑戰關卡二之按鈕隨機移動】 ===
152
+ # 提示:當點擊發生時,讓按鈕的 x 和 y 座標隨機分配在畫面內
153
+
154
+ button_rect.x = random.randint(0, WIDTH - button_rect.width)
155
+ button_rect.y = random.randint(0, HEIGHT - button_rect.height)
156
+ print(f" 魔法三指點擊!第 {click_count} 次")
157
+
158
+ # 更新上一幀狀態
159
+ was_pinching = is_pinching
160
+
161
+ # --- 繪製畫面 ---
162
+ screen.fill((20, 20, 35))
163
+ font = pygame.font.SysFont(None, 32)
164
+ large_font = pygame.font.SysFont(None, 64)
165
+
166
+ if game_state == "PLAYING":
167
+ # 繪製按鈕
168
+ btn_color = button_colors[current_color_index]
169
+ pygame.draw.rect(screen, btn_color, button_rect, border_radius=16)
170
+
171
+ btn_text = font.render(f"Clicks: {click_count}", True, (255, 255, 255))
172
+ screen.blit(btn_text, (button_rect.centerx - btn_text.get_width() // 2,
173
+ button_rect.centery - btn_text.get_height() // 2))
174
+
175
+ # 顯示倒數計時
176
+ time_text = font.render(f"Time Left: {remaining_time:.1f}s", True, (255, 220, 100))
177
+ screen.blit(time_text, (WIDTH - time_text.get_width() - 20, 10))
178
+
179
+ # 畫手指游標
180
+ cursor_color = (50, 255, 50) if is_pinching else (255, 50, 50)
181
+ pygame.draw.circle(screen, cursor_color, (finger_x, finger_y), 12) # 食指
182
+ pygame.draw.circle(screen, (255, 255, 255), (thumb_x, thumb_y), 12) # 大拇指
183
+ pygame.draw.circle(screen, (100, 150, 255), (middle_x, middle_y), 12) # 中指
184
+
185
+ # === 【TODO 5:挑戰關卡三之畫面提示】 ===
186
+ # 提示:當手不在畫面中時,在畫面上方顯示警告文字提醒學生將手部放回
187
+
188
+ if not hand_in_frame:
189
+ warning_text = font.render("OUT OF FRAME - PLEASE PUT HANDS BACK", True, (255, 100, 100))
190
+ screen.blit(warning_text, (WIDTH // 2 - warning_text.get_width() // 2, 100))
191
+
192
+ # 顯示調試數值
193
+ small_font = pygame.font.SysFont(None, 24)
194
+ status = small_font.render(f"Finger: ({finger_x}, {finger_y})", True, (150, 150, 150))
195
+ screen.blit(status, (10, 10))
196
+
197
+ dist_text = small_font.render(f"Dist Thumb-Index: {dist_thumb_index:.0f} px", True, (150, 150, 200))
198
+ screen.blit(dist_text, (10, 35))
199
+
200
+ dist_text2 = small_font.render(f"Dist Thumb-Middle: {dist_thumb_middle:.0f} px", True, (150, 150, 200))
201
+ screen.blit(dist_text2, (10, 60))
202
+
203
+ elif game_state == "GAME_OVER":
204
+ # 繪製遊戲結束畫面
205
+ over_text = large_font.render("GAME OVER", True, (255, 50, 50))
206
+ score_text = font.render(f"Total Clicks: {click_count} times", True, (255, 255, 255))
207
+
208
+ screen.blit(over_text, (WIDTH // 2 - over_text.get_width() // 2, HEIGHT // 2 - 50))
209
+ screen.blit(score_text, (WIDTH // 2 - score_text.get_width() // 2, HEIGHT // 2 + 20))
210
+
211
+ pygame.display.update()
212
+ clock.tick(30)
213
+
214
+ cap.release()
215
+ pygame.quit()
216
+ sys.exit()
 
projects/2026_AI營1/劉宗穎/三指捏遊戲/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Telearn Arcade Hub 入口檔(自動產生)
4
+ 實際執行: d5_pinch_challenge_solution.py
5
+ 規格: projects/{班級}/{學生}/{專案}/main.py
6
+ """
7
+ from pathlib import Path
8
+ import os
9
+ import runpy
10
+ import sys
11
+
12
+ _ROOT = Path(__file__).resolve().parent
13
+ os.chdir(_ROOT)
14
+ if str(_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_ROOT))
16
+
17
+ # 執行原始學員腳本
18
+ runpy.run_path(str(_ROOT / 'd5_pinch_challenge_solution.py'), run_name="__main__")
projects/2026_AI營1/劉宗穎/{人脸辨识 → 人臉辨識}/d3_2_face_detection.py RENAMED
File without changes
projects/2026_AI營1/劉宗穎/{人脸辨识 → 人臉辨識}/d3_2_face_detection2.py RENAMED
File without changes
projects/2026_AI營1/劉宗穎/人臉辨識/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Telearn Arcade Hub 入口檔(自動產生)
4
+ 實際執行: d3_2_face_detection2.py
5
+ 規格: projects/{班級}/{學生}/{專案}/main.py
6
+ """
7
+ from pathlib import Path
8
+ import os
9
+ import runpy
10
+ import sys
11
+
12
+ _ROOT = Path(__file__).resolve().parent
13
+ os.chdir(_ROOT)
14
+ if str(_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_ROOT))
16
+
17
+ # 執行原始學員腳本
18
+ runpy.run_path(str(_ROOT / 'd3_2_face_detection2.py'), run_name="__main__")
projects/2026_AI營1/劉宗穎/人臉辨識/onepiece(2).jpeg ADDED

Git LFS Details

  • SHA256: 16063edf453095349df30308f927b018b8b6eaf10cdcff3fd64dbe3e5c6c0cf6
  • Pointer size: 130 Bytes
  • Size of remote file: 80.2 kB
projects/2026_AI營1/劉宗穎/{咖啡店分析图表 → 咖啡店分析圖表}/coffee_sales_analysis.png RENAMED
File without changes
projects/2026_AI營1/劉宗穎/{咖啡店分析图表 → 咖啡店分析圖表}/coffee_sales_data.csv RENAMED
File without changes
projects/2026_AI營1/劉宗穎/{咖啡店分析图表 → 咖啡店分析圖表}/d1_4_visualization_coffee.py RENAMED
@@ -1,3 +1,5 @@
 
 
1
  # d1_4_visualization_coffee.py
2
  # Day 1: 資料科學與數據視覺化 - 咖啡廳營運數據進階分析(學員填空)
3
  # cd examples/Day1/student && python3 d1_4_visualization_coffee.py
@@ -8,6 +10,34 @@ import pandas as pd
8
  import matplotlib.pyplot as plt
9
 
10
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
11
  def load_csv(filename="coffee_sales_data.csv"):
12
  """安全載入 CSV 的輔助函式,用以防範在不同執行路徑下相對路徑讀檔失敗的問題"""
13
  # === 【學員填空練習點 1:CSV 檔案安全讀取】 ===
@@ -112,4 +142,4 @@ plt.tight_layout() # 自動調整間距,避免文字重疊
112
  output_image = "coffee_sales_analysis.png"
113
  # 請在下方完成圖表存檔與顯示:
114
  plt.savefig(output_image, dpi=300)
115
- plt.show()
 
1
+ import matplotlib
2
+ matplotlib.use('Agg') # Telearn hub headless
3
  # d1_4_visualization_coffee.py
4
  # Day 1: 資料科學與數據視覺化 - 咖啡廳營運數據進階分析(學員填空)
5
  # cd examples/Day1/student && python3 d1_4_visualization_coffee.py
 
10
  import matplotlib.pyplot as plt
11
 
12
 
13
+
14
+ def _telearn_plt_show(*_a, **_k):
15
+ """Hub-friendly plt.show: save current figure and display via OpenCV."""
16
+ import os
17
+ import matplotlib.pyplot as plt
18
+ try:
19
+ import cv2
20
+ except ImportError:
21
+ plt.savefig("_telearn_last_plot.png", dpi=150, bbox_inches="tight")
22
+ print("[Telearn] 已儲存 _telearn_last_plot.png(無 cv2 可顯示)")
23
+ return
24
+ path = os.path.join(os.getcwd(), "_telearn_last_plot.png")
25
+ plt.savefig(path, dpi=150, bbox_inches="tight")
26
+ img = cv2.imread(path)
27
+ if img is not None:
28
+ # fit to screen-ish width
29
+ h, w = img.shape[:2]
30
+ max_w = 960
31
+ if w > max_w:
32
+ scale = max_w / float(w)
33
+ img = cv2.resize(img, (int(w * scale), int(h * scale)))
34
+ cv2.imshow("Chart (press any key)", img)
35
+ print("[Telearn] 圖表已顯示,按任意鍵關閉…")
36
+ cv2.waitKey(0)
37
+ cv2.destroyAllWindows()
38
+ else:
39
+ print(f"[Telearn] 圖表已儲存: {path}")
40
+
41
  def load_csv(filename="coffee_sales_data.csv"):
42
  """安全載入 CSV 的輔助函式,用以防範在不同執行路徑下相對路徑讀檔失敗的問題"""
43
  # === 【學員填空練習點 1:CSV 檔案安全讀取】 ===
 
142
  output_image = "coffee_sales_analysis.png"
143
  # 請在下方完成圖表存檔與顯示:
144
  plt.savefig(output_image, dpi=300)
145
+ _telearn_plt_show()
projects/2026_AI營1/劉宗穎/咖啡店分析圖表/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Telearn Arcade Hub 入口檔(自動產生)
4
+ 實際執行: d1_4_visualization_coffee.py
5
+ 規格: projects/{班級}/{學生}/{專案}/main.py
6
+ """
7
+ from pathlib import Path
8
+ import os
9
+ import runpy
10
+ import sys
11
+
12
+ _ROOT = Path(__file__).resolve().parent
13
+ os.chdir(_ROOT)
14
+ if str(_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_ROOT))
16
+
17
+ # 執行原始學員腳本
18
+ runpy.run_path(str(_ROOT / 'd1_4_visualization_coffee.py'), run_name="__main__")
projects/2026_AI營1/劉宗穎/嘴巴偵測/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Telearn Arcade Hub 入口檔(自動產生)
4
+ 實際執行: step3_mouth_ratio.py
5
+ 規格: projects/{班級}/{學生}/{專案}/main.py
6
+ """
7
+ from pathlib import Path
8
+ import os
9
+ import runpy
10
+ import sys
11
+
12
+ _ROOT = Path(__file__).resolve().parent
13
+ os.chdir(_ROOT)
14
+ if str(_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_ROOT))
16
+
17
+ # 執行原始學員腳本
18
+ runpy.run_path(str(_ROOT / 'step3_mouth_ratio.py'), run_name="__main__")
projects/2026_AI營1/劉宗穎/嘴巴偵測/onepiece(2).jpeg ADDED

Git LFS Details

  • SHA256: 16063edf453095349df30308f927b018b8b6eaf10cdcff3fd64dbe3e5c6c0cf6
  • Pointer size: 130 Bytes
  • Size of remote file: 80.2 kB
projects/2026_AI營1/劉宗穎/{嘴巴侦测 → 嘴巴偵測}/step3_mouth_ratio.py RENAMED
@@ -1,113 +1,113 @@
1
- """
2
- Week 11 - Step 2: Mouth Opening Ratio Detection (Template 填空版)
3
- =============================================================
4
- 本步驟目標:
5
- 1. 找出特定的臉部特徵點:眼角(33, 263)與嘴唇(13, 14)
6
- 2. 計算兩眼距離,當作臉寬基準比例尺,防止人靠近或遠離鏡頭造成的誤判 (歸一化)
7
- 3. 計算嘴巴張開比例:嘴巴距離 / 兩眼距離
8
- 4. 在 OpenCV 畫面上即時顯示比例與嘴巴開合狀態 (OPEN / CLOSED)
9
- """
10
- import cv2
11
- import mediapipe as mp
12
- import math
13
- import sys
14
- import os
15
-
16
- # 1. 初始化攝影機
17
- cap = cv2.VideoCapture(0)
18
- if not cap.isOpened():
19
- print("Error: Could not open camera.")
20
- sys.exit()
21
-
22
- # 2. 載入 MediaPipe 臉部偵測器模型 (Face Landmarker)
23
- model_path = os.path.join('face_landmarker.task')
24
- if not os.path.exists(model_path):
25
- print(f"Error: Model file not found at {model_path}")
26
- sys.exit()
27
-
28
- try:
29
- from mediapipe.tasks import python
30
- from mediapipe.tasks.python import vision
31
- base_options = python.BaseOptions(model_asset_path=model_path)
32
- options = vision.FaceLandmarkerOptions(base_options=base_options, num_faces=1)
33
- detector = vision.FaceLandmarker.create_from_options(options)
34
- print("Face Landmarker initialized successfully.")
35
- except Exception as e:
36
- print(f"Error: Failed to initialize detector: {e}")
37
- sys.exit()
38
-
39
- # 特徵點索引定義
40
- LEFT_EYE_OUTER = 33
41
- RIGHT_EYE_OUTER = 263
42
- LIP_TOP = 13
43
- LIP_BOTTOM = 14
44
-
45
- # 嘴巴張開的比例閾值 (超過代表張開)
46
- THRESHOLD = 0.25
47
-
48
- print("Press 'q' in the window to exit.")
49
-
50
- # 3. 主迴圈
51
- while True:
52
- success, img = cap.read()
53
- if not success:
54
- break
55
-
56
- img = cv2.flip(img, 1)
57
- img_h, img_w, _ = img.shape
58
-
59
- # 影像處理
60
- img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
61
- mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=img_rgb)
62
- results = detector.detect(mp_image)
63
-
64
- if results.face_landmarks:
65
- face_lms = results.face_landmarks[0]
66
-
67
- # 【TODO 1】取得這 4 個特徵點在畫面中的 (x, y) 像素座標,並存在 pts 字典中
68
- pts = {}
69
- for idx in [LEFT_EYE_OUTER, RIGHT_EYE_OUTER, LIP_TOP, LIP_BOTTOM]:
70
- pt = face_lms[idx]
71
- pts[idx] = (int(pt.x * img_w), int(pt.y * img_h))
72
-
73
-
74
- # 【TODO 2】計算兩眼外角距離 (做為基準比例尺)
75
- p_lx, p_ly = pts[LEFT_EYE_OUTER]
76
- p_rx, p_ry = pts[RIGHT_EYE_OUTER]
77
- base_dist = math.hypot(p_rx - p_lx, p_ry - p_ly)
78
-
79
-
80
- if base_dist > 0:
81
- # 【TODO 3】計算嘴巴張開的實際像素距離,並除以眼距算出「嘴巴張開比例 (mouth_ratio)」
82
- p_top_x, p_top_y = pts[LIP_TOP]
83
- p_bot_x, p_bot_y = pts[LIP_BOTTOM]
84
- mouth_dist = math.hypot(p_bot_x - p_top_x, p_bot_y - p_top_y)
85
- mouth_ratio = mouth_dist / base_dist
86
-
87
-
88
- # 【TODO 4】判斷嘴巴狀態是否大於 THRESHOLD,並在畫面上繪製量測輔助線
89
- # 提示:用 cv2.line 畫出眼部基準線 (藍色) 和嘴巴測量線 (橘色)
90
- is_open = mouth_ratio > THRESHOLD
91
- cv2.line(img, (p_lx, p_ly), (p_rx, p_ry), (255, 0, 0), 2) # 藍色眼線
92
- cv2.line(img, (p_top_x, p_top_y), (p_bot_x, p_bot_y), (0, 165, 255), 2) # 橘色嘴線
93
-
94
-
95
- # 畫出特徵點小黃圓圈
96
- for idx, pos in pts.items():
97
- cv2.circle(img, pos, 5, (0, 255, 255), -1)
98
-
99
- # 顯示比例與開合狀態文字
100
- cv2.putText(img, f"Mouth Ratio: {mouth_ratio:.2f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
101
- status_text = "OPEN" if is_open else "CLOSED"
102
- status_color = (0, 255, 0) if is_open else (0, 0, 255)
103
- cv2.putText(img, f"State: {status_text}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, status_color, 2)
104
- else:
105
- cv2.putText(img, "No Face Detected", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
106
-
107
- cv2.imshow("Mouth Ratio Detector - Press 'q' to quit", img)
108
-
109
- if cv2.waitKey(1) & 0xFF == ord('q'):
110
- break
111
-
112
- cap.release()
113
- cv2.destroyAllWindows()
 
1
+ """
2
+ Week 11 - Step 2: Mouth Opening Ratio Detection (Template 填空版)
3
+ =============================================================
4
+ 本步驟目標:
5
+ 1. 找出特定的臉部特徵點:眼角(33, 263)與嘴唇(13, 14)
6
+ 2. 計算兩眼距離,當作臉寬基準比例尺,防止人靠近或遠離鏡頭造成的誤判 (歸一化)
7
+ 3. 計算嘴巴張開比例:嘴巴距離 / 兩眼距離
8
+ 4. 在 OpenCV 畫面上即時顯示比例與嘴巴開合狀態 (OPEN / CLOSED)
9
+ """
10
+ import cv2
11
+ import mediapipe as mp
12
+ import math
13
+ import sys
14
+ import os
15
+
16
+ # 1. 初始化攝影機
17
+ cap = cv2.VideoCapture(0)
18
+ if not cap.isOpened():
19
+ print("Error: Could not open camera.")
20
+ sys.exit()
21
+
22
+ # 2. 載入 MediaPipe 臉部偵���器模型 (Face Landmarker)
23
+ model_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'face_landmarker.task')
24
+ if not os.path.exists(model_path):
25
+ print(f"Error: Model file not found at {model_path}")
26
+ sys.exit()
27
+
28
+ try:
29
+ from mediapipe.tasks import python
30
+ from mediapipe.tasks.python import vision
31
+ base_options = python.BaseOptions(model_asset_path=model_path)
32
+ options = vision.FaceLandmarkerOptions(base_options=base_options, num_faces=1)
33
+ detector = vision.FaceLandmarker.create_from_options(options)
34
+ print("Face Landmarker initialized successfully.")
35
+ except Exception as e:
36
+ print(f"Error: Failed to initialize detector: {e}")
37
+ sys.exit()
38
+
39
+ # 特徵點索引定義
40
+ LEFT_EYE_OUTER = 33
41
+ RIGHT_EYE_OUTER = 263
42
+ LIP_TOP = 13
43
+ LIP_BOTTOM = 14
44
+
45
+ # 嘴巴張開的比例閾值 (超過代表張開)
46
+ THRESHOLD = 0.25
47
+
48
+ print("Press 'q' in the window to exit.")
49
+
50
+ # 3. 主迴圈
51
+ while True:
52
+ success, img = cap.read()
53
+ if not success:
54
+ break
55
+
56
+ img = cv2.flip(img, 1)
57
+ img_h, img_w, _ = img.shape
58
+
59
+ # 影像處理
60
+ img_rgb = cv2.cvtColor(img, cv2.COLOR_BGR2RGB)
61
+ mp_image = mp.Image(image_format=mp.ImageFormat.SRGB, data=img_rgb)
62
+ results = detector.detect(mp_image)
63
+
64
+ if results.face_landmarks:
65
+ face_lms = results.face_landmarks[0]
66
+
67
+ # 【TODO 1】取得這 4 個特徵點在畫面中的 (x, y) 像素座標,並存在 pts 字典中
68
+ pts = {}
69
+ for idx in [LEFT_EYE_OUTER, RIGHT_EYE_OUTER, LIP_TOP, LIP_BOTTOM]:
70
+ pt = face_lms[idx]
71
+ pts[idx] = (int(pt.x * img_w), int(pt.y * img_h))
72
+
73
+
74
+ # 【TODO 2】計算兩眼外角距離 (做為基準比例尺)
75
+ p_lx, p_ly = pts[LEFT_EYE_OUTER]
76
+ p_rx, p_ry = pts[RIGHT_EYE_OUTER]
77
+ base_dist = math.hypot(p_rx - p_lx, p_ry - p_ly)
78
+
79
+
80
+ if base_dist > 0:
81
+ # 【TODO 3】計算嘴巴張開的實際像素距離,並除以眼距算出「嘴巴張開比例 (mouth_ratio)」
82
+ p_top_x, p_top_y = pts[LIP_TOP]
83
+ p_bot_x, p_bot_y = pts[LIP_BOTTOM]
84
+ mouth_dist = math.hypot(p_bot_x - p_top_x, p_bot_y - p_top_y)
85
+ mouth_ratio = mouth_dist / base_dist
86
+
87
+
88
+ # 【TODO 4】判斷嘴巴狀態是否大於 THRESHOLD,並在畫面上繪製量測輔助線
89
+ # 提示:用 cv2.line 畫出眼部基準線 (藍色) 和嘴巴測量線 (橘色)
90
+ is_open = mouth_ratio > THRESHOLD
91
+ cv2.line(img, (p_lx, p_ly), (p_rx, p_ry), (255, 0, 0), 2) # 藍色眼線
92
+ cv2.line(img, (p_top_x, p_top_y), (p_bot_x, p_bot_y), (0, 165, 255), 2) # 橘色嘴線
93
+
94
+
95
+ # 畫出特徵點小黃圓圈
96
+ for idx, pos in pts.items():
97
+ cv2.circle(img, pos, 5, (0, 255, 255), -1)
98
+
99
+ # 顯示比例與開合狀態文字
100
+ cv2.putText(img, f"Mouth Ratio: {mouth_ratio:.2f}", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (255, 255, 255), 2)
101
+ status_text = "OPEN" if is_open else "CLOSED"
102
+ status_color = (0, 255, 0) if is_open else (0, 0, 255)
103
+ cv2.putText(img, f"State: {status_text}", (10, 60), cv2.FONT_HERSHEY_SIMPLEX, 0.7, status_color, 2)
104
+ else:
105
+ cv2.putText(img, "No Face Detected", (10, 30), cv2.FONT_HERSHEY_SIMPLEX, 0.7, (0, 0, 255), 2)
106
+
107
+ cv2.imshow("Mouth Ratio Detector - Press 'q' to quit", img)
108
+
109
+ if cv2.waitKey(1) & 0xFF == ord('q'):
110
+ break
111
+
112
+ cap.release()
113
+ cv2.destroyAllWindows()
projects/2026_AI營1/劉宗穎/{天气资料图表 → 天氣資料圖表}/d1_3_visualization.py RENAMED
@@ -1,87 +1,117 @@
1
- # d1_3_visualization.py
2
- # Day 1: 資料科學與數據視覺化 - Matplotlib 繪圖基礎(學員填空)
3
- # cd examples/Day1/student && python3 d1_3_visualization.py
4
- # 參考答案:../Teacher/d1_3_visualization.py
5
-
6
- import os
7
- import pandas as pd
8
- import matplotlib.pyplot as plt
9
-
10
- def load_csv(filename="taipei_weather_2026.csv"):
11
- """安全載入 CSV 的輔助函式,用以防範在不同執行路徑下相對路徑讀檔失敗的問題"""
12
- # === 【學員填空練習點 1:CSV 檔案安全讀取】 ===
13
- # TODO: 1. 使用 os.path.join 將當前腳本所在的目錄與檔案名稱拼接成絕對路徑
14
- # 提示: 當前腳本目錄為 os.path.dirname(__file__)
15
- # 2. 使用 os.path.exists 判斷路徑是否存在,若存在則用 pd.read_csv 讀取並回傳
16
- path = os.path.join(os.path.dirname(__file__), filename)
17
-
18
- if os.path.exists(path):
19
- return pd.read_csv(path)
20
- else:
21
- print("error: no files found")
22
-
23
- df = load_csv()
24
-
25
- # 設定中文字型與負號顯示防禦,避免圖表出現亂碼方塊
26
- plt.rcParams['font.sans-serif'] = [
27
- 'PingFang TC', 'Heiti TC', 'Arial Unicode MS',
28
- 'Microsoft JhengHei', 'Noto Sans CJK TC', 'SimHei',
29
- ]
30
- plt.rcParams['axes.unicode_minus'] = False
31
-
32
- # === 【學員填空練習點 2:建立子圖畫布 (Subplots)】 ===
33
- # TODO: 使用 plt.subplots 建立一個 2 列 1 行 (2x1) 的子圖畫布,尺寸大小為寬 10 英吋、高 8 英吋
34
- # 並指派給 fig 與 (ax1, ax2)
35
- fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
36
-
37
- # 1. 繪製折線圖 (Line Plot) - 每日溫度
38
- # === 【學員填空練習點 3:繪製每日溫度折線圖】 ===
39
- # TODO: 1. 在 ax1 畫布上以 ax1.plot() 繪製折線圖,X軸傳入 df['Date'],Y軸傳入 df['Temperature']
40
- # 設定參數:帶有圓點 marker='o',顏色 color='crimson',圖例 label='溫度 (°C)',線寬 linewidth=2
41
- # 2. 使用 ax1.set_title 設定標題 "台北觀測站每日溫度趨勢圖"
42
- # 3. 設定 ax1.set_xlabel("日期") 與 ax1.set_ylabel("數值")
43
- # 4. 設定網格 ax1.grid(True, linestyle='--', alpha=0.5)
44
- # 5. 呼叫 ax1.legend() 顯示圖例
45
- # 6. 設定 X軸刻度旋轉 ax1.tick_params(axis='x', rotation=45)
46
- #
47
- # 請在下方完成 ax1 的溫度折線圖繪製:
48
- ax1.plot(df['Date'], df['Temperature'], marker='o', color= 'crimson',label='温度 (°C)', linewidth=2)
49
- ax1.set_title("台北观测站每日温度趋势图", fontsize=14, fontweight='bold')
50
- ax1.set_xlabel("日期")
51
-
52
- ax1.set_ylabel("数值")
53
- ax1.grid(True,linestyle="--", alpha=0.5)
54
- ax1.legend()
55
- ax1.tick_params(axis='x', rotation=45)
56
- ax1.plot(df['Date'], df['Humidity'], marker='o', color='lime', label='湿度 (%)', linewidth=2)
57
- ax1.legend()
58
-
59
- # 2. 繪製條形圖 (Bar Plot) - 每日降雨量
60
- # === 【學員填空練習點 4:繪製每日降雨量長條圖】 ===
61
- # TODO: 1. 在 ax2 畫布上以 ax2.bar() 繪製長條圖,X軸傳入 df['Date'],Y軸傳入 df['Rainfall']
62
- # 設定參數:顏色 color='skyblue',外框顏色 edgecolor='royalblue',圖例 label='降雨量 (mm)'
63
- # 2. 使用 ax2.set_title 設定標題 "台北觀測站每日降雨量分佈"
64
- # 3. 設定 ax2.set_xlabel("日期") 與 ax2.set_ylabel("降雨量 (mm)")
65
- # 4. 設定 Y 軸網格 ax2.grid(True, axis='y', linestyle='--', alpha=0.5)
66
- # 5. 呼叫 ax2.legend() 顯示圖例
67
- # 6. 設定 X軸刻度旋轉 ax2.tick_params(axis='x', rotation=45)
68
- #
69
- # 請在下方完成 ax2 的降雨量長條圖繪製:
70
- ax2.bar(df['Date'], df['Rainfall'], color='turquoise',edgecolor = 'turquoise',label='降雨量 (mm)')
71
- ax2.set_title("台北每天降雨量长条图", fontsize=14, fontweight='bold')
72
- ax2.set_xlabel("日期")
73
- ax2.set_ylabel("降雨量")
74
- ax2.grid(True, axis='y', linestyle='--', alpha=0.5)
75
- ax2.legend()
76
- ax2.tick_params(axis='x', rotation=45)
77
-
78
- plt.tight_layout() # 自動調整間距,避免文字重疊
79
-
80
- # === 【學員填空練習點 5:圖表自動儲存與顯示】 ===
81
- # TODO: 1. 呼叫 plt.savefig 將畫布儲存為檔名 "taipei_weather_2026.png",並設定高畫質 dpi=300
82
- # (⚠️ 注意:savefig 必須在 show() 之前呼叫)
83
- # 2. 呼叫 plt.show() 將圖表視窗顯示在畫面上
84
- #
85
- # 請在下方完成圖表存檔與顯示:
86
- plt.savefig("taipei_weather_2026.png", dpi=300)
87
- plt.show()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import matplotlib
2
+ matplotlib.use('Agg') # Telearn hub headless
3
+ # d1_3_visualization.py
4
+ # Day 1: 資料科學與數據視覺化 - Matplotlib 繪圖基礎(學員填空)
5
+ # cd examples/Day1/student && python3 d1_3_visualization.py
6
+ # 參考答案:../Teacher/d1_3_visualization.py
7
+
8
+ import os
9
+ import pandas as pd
10
+ import matplotlib.pyplot as plt
11
+
12
+
13
+ def _telearn_plt_show(*_a, **_k):
14
+ """Hub-friendly plt.show: save current figure and display via OpenCV."""
15
+ import os
16
+ import matplotlib.pyplot as plt
17
+ try:
18
+ import cv2
19
+ except ImportError:
20
+ plt.savefig("_telearn_last_plot.png", dpi=150, bbox_inches="tight")
21
+ print("[Telearn] 已儲存 _telearn_last_plot.png(無 cv2 可顯示)")
22
+ return
23
+ path = os.path.join(os.getcwd(), "_telearn_last_plot.png")
24
+ plt.savefig(path, dpi=150, bbox_inches="tight")
25
+ img = cv2.imread(path)
26
+ if img is not None:
27
+ # fit to screen-ish width
28
+ h, w = img.shape[:2]
29
+ max_w = 960
30
+ if w > max_w:
31
+ scale = max_w / float(w)
32
+ img = cv2.resize(img, (int(w * scale), int(h * scale)))
33
+ cv2.imshow("Chart (press any key)", img)
34
+ print("[Telearn] 圖表已顯示,按任意鍵關閉…")
35
+ cv2.waitKey(0)
36
+ cv2.destroyAllWindows()
37
+ else:
38
+ print(f"[Telearn] 圖表已儲存: {path}")
39
+
40
+ def load_csv(filename="taipei_weather_2026.csv"):
41
+ """安全載入 CSV 的輔助函式,用以防範在不同執行路徑下相對路徑讀檔失敗的問題"""
42
+ # === 【學員填空練習點 1:CSV 檔案安全讀取】 ===
43
+ # TODO: 1. 使用 os.path.join 將當前腳本所在的目錄與檔案名稱拼接成絕對路徑
44
+ # 提示: 當前腳本目錄為 os.path.dirname(__file__)
45
+ # 2. 使用 os.path.exists 判斷路徑是否存在,若存在則用 pd.read_csv 讀取並回傳
46
+ path = os.path.join(os.path.dirname(__file__), filename)
47
+
48
+ if os.path.exists(path):
49
+ return pd.read_csv(path)
50
+ else:
51
+ print("error: no files found")
52
+
53
+ df = load_csv()
54
+
55
+ # 設定中文字型與負號顯示防禦,避免圖表出現亂碼方塊
56
+ plt.rcParams['font.sans-serif'] = [
57
+ 'PingFang TC', 'Heiti TC', 'Arial Unicode MS',
58
+ 'Microsoft JhengHei', 'Noto Sans CJK TC', 'SimHei',
59
+ ]
60
+ plt.rcParams['axes.unicode_minus'] = False
61
+
62
+ # === 【學員填空練習點 2:建立子圖畫布 (Subplots)】 ===
63
+ # TODO: 使用 plt.subplots 建立一個 2 列 1 行 (2x1) 的子圖畫布,尺寸大小為寬 10 英吋、高 8 英吋
64
+ # 並指派給 fig 與 (ax1, ax2)
65
+ fig, (ax1, ax2) = plt.subplots(2, 1, figsize=(10, 8))
66
+
67
+ # 1. 繪製折線圖 (Line Plot) - 每日溫度
68
+ # === 【學員填空練習點 3:繪製每日溫度折線圖】 ===
69
+ # TODO: 1. 在 ax1 畫布上以 ax1.plot() 繪製折線圖,X軸傳入 df['Date'],Y軸傳入 df['Temperature']
70
+ # 設定參數:帶有圓點 marker='o',顏色 color='crimson',圖例 label='溫度 (°C)',線寬 linewidth=2
71
+ # 2. 使用 ax1.set_title 設定標題 "台北觀測站每日溫度趨勢圖"
72
+ # 3. 設定 ax1.set_xlabel("日期") 與 ax1.set_ylabel("數值")
73
+ # 4. 設定網格 ax1.grid(True, linestyle='--', alpha=0.5)
74
+ # 5. 呼叫 ax1.legend() 顯示圖例
75
+ # 6. 設定 X軸刻度旋轉 ax1.tick_params(axis='x', rotation=45)
76
+ #
77
+ # 請在下方完成 ax1 的溫度折線圖繪製:
78
+ ax1.plot(df['Date'], df['Temperature'], marker='o', color= 'crimson',label='温度 (°C)', linewidth=2)
79
+ ax1.set_title("台北观测站每日温度趋势图", fontsize=14, fontweight='bold')
80
+ ax1.set_xlabel("日期")
81
+
82
+ ax1.set_ylabel("数值")
83
+ ax1.grid(True,linestyle="--", alpha=0.5)
84
+ ax1.legend()
85
+ ax1.tick_params(axis='x', rotation=45)
86
+ ax1.plot(df['Date'], df['Humidity'], marker='o', color='lime', label='湿度 (%)', linewidth=2)
87
+ ax1.legend()
88
+
89
+ # 2. 繪製條形圖 (Bar Plot) - 每日降雨量
90
+ # === 【學員填空練習點 4:繪製每日降雨量長條圖】 ===
91
+ # TODO: 1. 在 ax2 畫布上以 ax2.bar() 繪製長條圖,X軸傳入 df['Date'],Y軸傳入 df['Rainfall']
92
+ # 設定參數:顏色 color='skyblue',外框顏色 edgecolor='royalblue',圖例 label='降雨量 (mm)'
93
+ # 2. 使用 ax2.set_title 設定標題 "台北觀測站每日降雨量分佈"
94
+ # 3. 設定 ax2.set_xlabel("日期") 與 ax2.set_ylabel("降雨量 (mm)")
95
+ # 4. 設定 Y 軸網格 ax2.grid(True, axis='y', linestyle='--', alpha=0.5)
96
+ # 5. 呼叫 ax2.legend() 顯示圖例
97
+ # 6. 設定 X軸刻度旋轉 ax2.tick_params(axis='x', rotation=45)
98
+ #
99
+ # 請在下方完成 ax2 的降雨量長條圖繪製:
100
+ ax2.bar(df['Date'], df['Rainfall'], color='turquoise',edgecolor = 'turquoise',label='降雨量 (mm)')
101
+ ax2.set_title("台北每天降雨量长条图", fontsize=14, fontweight='bold')
102
+ ax2.set_xlabel("日期")
103
+ ax2.set_ylabel("降雨量")
104
+ ax2.grid(True, axis='y', linestyle='--', alpha=0.5)
105
+ ax2.legend()
106
+ ax2.tick_params(axis='x', rotation=45)
107
+
108
+ plt.tight_layout() # 自動調整間距,避免文字重疊
109
+
110
+ # === 【學員填空練習點 5:圖表自動儲存與顯示】 ===
111
+ # TODO: 1. 呼叫 plt.savefig 將畫布儲存為檔名 "taipei_weather_2026.png",並設定高畫質 dpi=300
112
+ # (⚠️ 注意:savefig 必須在 show() 之前呼叫)
113
+ # 2. 呼叫 _telearn_plt_show() 將圖表視窗顯示在畫面上
114
+ #
115
+ # 請在下方完成圖表存檔與顯示:
116
+ plt.savefig("taipei_weather_2026.png", dpi=300)
117
+ _telearn_plt_show()
projects/2026_AI營1/劉宗穎/天氣資料圖表/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Telearn Arcade Hub 入口檔(自動產生)
4
+ 實際執行: d1_3_visualization.py
5
+ 規格: projects/{班級}/{學生}/{專案}/main.py
6
+ """
7
+ from pathlib import Path
8
+ import os
9
+ import runpy
10
+ import sys
11
+
12
+ _ROOT = Path(__file__).resolve().parent
13
+ os.chdir(_ROOT)
14
+ if str(_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_ROOT))
16
+
17
+ # 執行原始學員腳本
18
+ runpy.run_path(str(_ROOT / 'd1_3_visualization.py'), run_name="__main__")
projects/2026_AI營1/劉宗穎/天氣資料圖表/onepiece(2).jpeg ADDED

Git LFS Details

  • SHA256: 16063edf453095349df30308f927b018b8b6eaf10cdcff3fd64dbe3e5c6c0cf6
  • Pointer size: 130 Bytes
  • Size of remote file: 80.2 kB
projects/2026_AI營1/劉宗穎/{天气资料图表 → 天氣資料圖表}/taipei_weather_2026.csv RENAMED
File without changes
projects/2026_AI營1/劉宗穎/{影像处理 → 影像處理}/d3_1_opencv_basic.py RENAMED
@@ -1,70 +1,110 @@
1
- # d3_1_opencv_basic.py
2
- # Day 3: 電腦視覺與影像辨識 - OpenCV 影像處理基礎 (學員練習版)
3
- # 練習指引:依序找到程式碼中的 TODO 1 到 TODO 5,將其下方的程式碼「解除註釋(刪除行首的 # )」並執行,觀察影像處理管線中每一步的變化。
4
-
5
- import cv2
6
- import numpy as np
7
-
8
- # 1. 影像讀取 (Read)
9
- img = cv2.imread("onepiece(2).jpeg")
10
- if img is None:
11
- print("[錯誤] 無法讀取 jpg")
12
- exit(1)
13
-
14
- # 預設灰階影像為原始彩色影像,避免未解開註釋時程式出錯
15
- gray = img
16
-
17
- # === 【重要觀念 TODO 1:灰階轉換 (Grayscale Conversion)】 ===
18
- # 觀念重點:大多數電腦視覺演算法在處理單通道(黑白)影像時速度更快。
19
- # 將 BGR 彩色圖像轉換為單通道灰階圖像,可以減少 2/3 的資料量,且能過濾掉無用的顏色雜訊。
20
- # 請解開下方註釋 (刪除行首的 #) 並執行,觀察變為灰階後的影像。
21
- gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
22
-
23
- # 預設模糊影像為灰階影像,避免未解開註釋時程式出錯
24
- blurred = gray
25
-
26
- # === 【重要觀念 TODO 2:高斯模糊平均來平 (Gaussian Blur)】 ===
27
- # 觀念重點:高斯模糊是透過像素加權滑影像,主要目的是「消除噪點和細微反光」。
28
- # 這可以避免後續邊緣偵測時把細微的噪輪廓,讓偵測出來的邊緣更加平滑。
29
- # 請解開下方註釋 (刪除行首的 #) 並執行,觀察影像邊緣的柔焦效果。
30
- blurred = cv2.GaussianBlur(gray, (5, 5), 0)
31
-
32
- # 預設邊緣影像為全黑畫布,避免未解開註釋時點誤判為程式出錯
33
- edges = np.zeros(img.shape[:2], dtype=np.uint8)
34
-
35
- # === 【重要觀念 TODO 3:Canny 邊緣偵測 (Canny Edge Detection)】 ===
36
- # 觀念重點:Canny 演算法會找出影像中明暗變化劇烈的分界線。它使用雙閥值法:
37
- # 高於高閥值(150)的 100% 判定為邊緣;介於高低閥值(50~150)之間的若連接強邊緣則保留;其餘捨棄。
38
- # 請解開下方註釋 (刪除行首的 #) 並執行,觀察提取出的白色線條輪廓。
39
- edges = cv2.Canny(blurred, 10, 80)
40
-
41
- # 預設無偵測到輪廓,且繪製圖像為原始圖像,避免未解開註釋時程式出錯
42
- contours = []
43
- contour_img = img.copy()
44
-
45
- # === 【重要觀念 TODO 4:尋找與繪製輪廓 (Find & Draw Contours)】 ===
46
- # 觀念重點:`findContours` 會在二值化(邊緣)影像中尋找相連的邊界,並建立幾何結構。
47
- # `drawContours` 則會將這些幾何結構繪製到指定的影像上(此處使用綠色框繪製於原始彩色影像)。
48
- # 請解開下方所有註釋 (刪除行首的 #) 並執行,觀察圖形外框被綠線框起來的結果。
49
- contours, hierarchy = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
50
- print(f"在影像中偵測到 {len(contours)} 個獨立輪廓特徵。")
51
- contour_img = img.copy()
52
- cv2.drawContours(contour_img, contours, -1, (173,216,203), 1)
53
- alpha=1
54
-
55
- # === 【重要觀念 TODO 5:視窗顯示與釋放 (Window Display)】 ===
56
- # 觀念重點:使用 cv2.imshow 顯示各步驟處理結果,並用 waitKey(0) 讓程式停下等待按鍵,
57
- # 最後使用 destroyAllWindows 釋放視窗資源。
58
- # 請解開下方所有註釋 (刪除行首的 #),以觀看影像處理的成果!
59
- print("\n正在顯示影像處理結果(請點擊影像視窗並按下任意鍵關閉)...")
60
- cv2.imshow("Original Shapes", img)
61
- cv2.imshow("Grayscale", gray)
62
- cv2.imshow("Canny Edges", edges)
63
- cv2.imshow("Contours Output", contour_img)
64
-
65
- success_gray = cv2.imwrite("output_gray.jpg", gray)
66
- success_edges = cv2.imwrite("output_edges.jpg", edges)
67
- success_contour = cv2.imwrite("output_contours.jpg", contour_img)
68
-
69
- cv2.waitKey(0)
70
- cv2.destroyAllWindows()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # d3_1_opencv_basic.py
2
+ # Day 3: 電腦視覺與影像辨識 - OpenCV 影像處理基礎 (學員練習版)
3
+ # 練習指引:依序找到程式碼中的 TODO 1 到 TODO 5,將其下方的程式碼「解除註釋(刪除行首的 # )」並執行,觀察影像處理管線中每一步的變化。
4
+
5
+ import cv2
6
+ import numpy as np
7
+
8
+ # 1. 影像讀取 (Read)
9
+
10
+ def telearn_resolve_image(name):
11
+ """Resolve image path relative to script, cwd, or student image/ folders."""
12
+ import os
13
+ from pathlib import Path
14
+ here = Path(__file__).resolve().parent
15
+ candidates = [
16
+ Path(name),
17
+ here / name,
18
+ here / "image" / name,
19
+ here / "照片" / name,
20
+ here.parent / "image" / name,
21
+ here.parent / "照片" / name,
22
+ here.parent / "image_create" / name,
23
+ ]
24
+ # also try basename-only search one level
25
+ for base in [here, here.parent]:
26
+ for folder in ["image", "照片", "image_create", "处理过的照片"]:
27
+ p = base / folder
28
+ if p.is_dir():
29
+ direct = p / name
30
+ candidates.append(direct)
31
+ # case-insensitive match
32
+ low = name.lower()
33
+ try:
34
+ for f in p.iterdir():
35
+ if f.is_file() and f.name.lower() == low:
36
+ candidates.append(f)
37
+ except OSError:
38
+ pass
39
+ for c in candidates:
40
+ try:
41
+ if c.is_file():
42
+ print(f"[Telearn] 讀取影像: {c}")
43
+ return str(c)
44
+ except OSError:
45
+ pass
46
+ print(f"[Telearn] 找不到影像 {name},搜尋過: {[str(c) for c in candidates[:8]]}…")
47
+ return name
48
+
49
+ img = cv2.imread(telearn_resolve_image("onepiece(2).jpeg"))
50
+ if img is None:
51
+ print("[錯誤] 無法讀取 jpg")
52
+ exit(1)
53
+
54
+ # 預設灰階影像為原始彩色影像,避免未解開註釋時程式出錯
55
+ gray = img
56
+
57
+ # === 【重要觀念 TODO 1:灰階轉換 (Grayscale Conversion)】 ===
58
+ # 觀念重點:大多數電腦視覺演算法在處理單通道(黑白)影像時速度更快。
59
+ # 將 BGR 彩色圖像轉換為單通道灰階圖像,可以減少 2/3 的資料量,且能過濾掉無用的顏色雜訊。
60
+ # 請解開下方註釋 (刪除行首的 #) 並執行,觀察變為灰階後的影像。
61
+ gray = cv2.cvtColor(img, cv2.COLOR_BGR2GRAY)
62
+
63
+ # 預設模糊影像為灰階影像,避免未解開註釋時程式出錯
64
+ blurred = gray
65
+
66
+ # === 【重要觀念 TODO 2:高斯模糊平均來平 (Gaussian Blur)】 ===
67
+ # 觀念重點:高斯模糊是透過像素加權滑影像,主要目的是「消除噪點和細微反光」。
68
+ # 這可以避免後續邊緣偵測時把細微的噪輪廓,讓偵測出來的邊緣更加平滑。
69
+ # 請解開下方註釋 (刪除行首的 #) 並執行,觀察影像邊緣的柔焦效果。
70
+ blurred = cv2.GaussianBlur(gray, (5, 5), 0)
71
+
72
+ # 預設邊緣影像為全黑畫布,避免未解開註釋時點誤判為程式出錯
73
+ edges = np.zeros(img.shape[:2], dtype=np.uint8)
74
+
75
+ # === 【重要觀念 TODO 3:Canny 邊緣偵測 (Canny Edge Detection)】 ===
76
+ # 觀念重點:Canny 演算法會找出影像中明暗變化劇烈的分界線。它使用雙閥值法:
77
+ # 高於高閥值(150)的 100% 判定為邊緣;介於高低閥值(50~150)之間的若連接強邊緣則保留;其餘捨棄。
78
+ # 請解開下方註釋 (刪除行首的 #) 並執行,觀察提取出的白色線條輪廓。
79
+ edges = cv2.Canny(blurred, 10, 80)
80
+
81
+ # 預設無偵測到輪廓,且繪製圖像為原始圖像,避免未解開註釋時程式出錯
82
+ contours = []
83
+ contour_img = img.copy()
84
+
85
+ # === 【重要觀念 TODO 4:尋找與繪製輪廓 (Find & Draw Contours)】 ===
86
+ # 觀念重點:`findContours` 會在二值化(邊緣)影像中尋找相連的邊界,並建立幾何結構。
87
+ # `drawContours` 則會將這些幾何結構繪製到指定的影像上(此處使用綠色框繪製於原始彩色影像)。
88
+ # 請解開下方所有註釋 (刪除行首的 #) 並執行,觀察圖形外框被綠線框起來的結果。
89
+ contours, hierarchy = cv2.findContours(edges, cv2.RETR_EXTERNAL, cv2.CHAIN_APPROX_SIMPLE)
90
+ print(f"在影像中偵測到 {len(contours)} 個獨立輪廓特徵。")
91
+ contour_img = img.copy()
92
+ cv2.drawContours(contour_img, contours, -1, (173,216,203), 1)
93
+ alpha=1
94
+
95
+ # === 【重要觀念 TODO 5:視窗顯示與釋放 (Window Display)】 ===
96
+ # 觀念重點:使用 cv2.imshow 顯示各步驟處理結果,並用 waitKey(0) 讓程式停下等待按鍵,
97
+ # 最後使用 destroyAllWindows 釋放視窗資源。
98
+ # 請解開下方所有註釋 (刪除行首的 #),以觀看影像處理的成果!
99
+ print("\n正在顯示影像處理結果(請點擊影像視窗並按下任意鍵關閉)...")
100
+ cv2.imshow("Original Shapes", img)
101
+ cv2.imshow("Grayscale", gray)
102
+ cv2.imshow("Canny Edges", edges)
103
+ cv2.imshow("Contours Output", contour_img)
104
+
105
+ success_gray = cv2.imwrite("output_gray.jpg", gray)
106
+ success_edges = cv2.imwrite("output_edges.jpg", edges)
107
+ success_contour = cv2.imwrite("output_contours.jpg", contour_img)
108
+
109
+ cv2.waitKey(0)
110
+ cv2.destroyAllWindows()
projects/2026_AI營1/劉宗穎/影像處理/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Telearn Arcade Hub 入口檔(自動產生)
4
+ 實際執行: d3_1_opencv_basic.py
5
+ 規格: projects/{班級}/{學生}/{專案}/main.py
6
+ """
7
+ from pathlib import Path
8
+ import os
9
+ import runpy
10
+ import sys
11
+
12
+ _ROOT = Path(__file__).resolve().parent
13
+ os.chdir(_ROOT)
14
+ if str(_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_ROOT))
16
+
17
+ # 執行原始學員腳本
18
+ runpy.run_path(str(_ROOT / 'd3_1_opencv_basic.py'), run_name="__main__")
projects/2026_AI營1/劉宗穎/影像處理/onepiece(2).jpeg ADDED

Git LFS Details

  • SHA256: 16063edf453095349df30308f927b018b8b6eaf10cdcff3fd64dbe3e5c6c0cf6
  • Pointer size: 130 Bytes
  • Size of remote file: 80.2 kB
projects/2026_AI營1/劉宗穎/{手指画画游戏 → 手指畫畫遊戲}/draw_game_student.py RENAMED
@@ -1,3 +1,4 @@
 
1
  import cv2
2
  import numpy as np
3
  import mediapipe as mp
@@ -14,7 +15,7 @@ from mediapipe.tasks.python import vision
14
  # 2. HandLandmarkerOptions 用於設定手部偵測的細項設定,例如 num_hands=1 代表最多偵測一隻手。
15
  # 3. 最後透過 vision.HandLandmarker.create_from_options 建立手部偵測器物件。
16
  # 請解開下方三行程式碼的註釋:
17
- base_options = python.BaseOptions(model_asset_path='hand_landmarker.task')
18
  options = vision.HandLandmarkerOptions(base_options=base_options, num_hands=1)
19
  detector = vision.HandLandmarker.create_from_options(options)
20
 
 
1
+ import os
2
  import cv2
3
  import numpy as np
4
  import mediapipe as mp
 
15
  # 2. HandLandmarkerOptions 用於設定手部偵測的細項設定,例如 num_hands=1 代表最多偵測一隻手。
16
  # 3. 最後透過 vision.HandLandmarker.create_from_options 建立手部偵測器物件。
17
  # 請解開下方三行程式碼的註釋:
18
+ base_options = python.BaseOptions(model_asset_path=os.path.join(os.path.dirname(os.path.abspath(__file__)), 'hand_landmarker.task'))
19
  options = vision.HandLandmarkerOptions(base_options=base_options, num_hands=1)
20
  detector = vision.HandLandmarker.create_from_options(options)
21
 
projects/2026_AI營1/劉宗穎/手指畫畫遊戲/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Telearn Arcade Hub 入口檔(自動產生)
4
+ 實際執行: draw_game_student.py
5
+ 規格: projects/{班級}/{學生}/{專案}/main.py
6
+ """
7
+ from pathlib import Path
8
+ import os
9
+ import runpy
10
+ import sys
11
+
12
+ _ROOT = Path(__file__).resolve().parent
13
+ os.chdir(_ROOT)
14
+ if str(_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_ROOT))
16
+
17
+ # 執行原始學員腳本
18
+ runpy.run_path(str(_ROOT / 'draw_game_student.py'), run_name="__main__")
projects/2026_AI營1/劉宗穎/手指畫畫遊戲/onepiece(2).jpeg ADDED

Git LFS Details

  • SHA256: 16063edf453095349df30308f927b018b8b6eaf10cdcff3fd64dbe3e5c6c0cf6
  • Pointer size: 130 Bytes
  • Size of remote file: 80.2 kB
projects/2026_AI營1/劉宗穎/{爬虫分析 → 爬蟲分析}/d4_1_ex1_books.py RENAMED
File without changes
projects/2026_AI營1/劉宗穎/{爬虫分析 → 爬蟲分析}/d4_1_ex2_ptt.py RENAMED
File without changes
projects/2026_AI營1/劉宗穎/{爬虫分析 → 爬蟲分析}/d4_1_ex3_ithelp.py RENAMED
File without changes
projects/2026_AI營1/劉宗穎/爬蟲分析/main.py ADDED
@@ -0,0 +1,26 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """多練習選單入口(自動產生)"""
3
+ from pathlib import Path
4
+ import os
5
+ import runpy
6
+ import sys
7
+
8
+ _ROOT = Path(__file__).resolve().parent
9
+ os.chdir(_ROOT)
10
+ sys.path.insert(0, str(_ROOT))
11
+
12
+ CHOICES = {
13
+ "1": 'd4_1_ex1_books.py',
14
+ "2": 'd4_1_ex2_ptt.py',
15
+ "3": 'd4_1_ex3_ithelp.py',
16
+ }
17
+
18
+ print("=== 本資料夾含多個練習,請選擇 ===")
19
+ print(' 1. d4_1_ex1_books.py')
20
+ print(' 2. d4_1_ex2_ptt.py')
21
+ print(' 3. d4_1_ex3_ithelp.py')
22
+ print(" (直接 Enter 預設 1)")
23
+ sel = input("編號: ").strip() or "1"
24
+ name = CHOICES.get(sel) or CHOICES.get("1")
25
+ print(f"執行: {name}")
26
+ runpy.run_path(str(_ROOT / name), run_name="__main__")
projects/2026_AI營1/劉宗穎/爬蟲分析_PTT/d4_1_ex2_ptt.py ADDED
@@ -0,0 +1,81 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # d4_1_ex2_ptt.py — Day 4 學員填空
2
+ # cd examples/Day4/student && python d4_1_ex2_ptt.py
3
+ # 參考答案:../d4_1_ex2_ptt.py
4
+
5
+ import requests
6
+ from bs4 import BeautifulSoup
7
+ from urllib.parse import urljoin
8
+
9
+ # 目標網站:PTT Python 討論板首頁
10
+ URL = "https://www.ptt.cc/bbs/Python/index.html"
11
+
12
+ # === 【學員填空練習點 1:模擬瀏覽器 Header】 ===
13
+ # TODO: 填入瀏覽器的 User-Agent,否則 PTT 會拒絕請求並回傳 403 錯誤
14
+ HEADERS = {
15
+ "User-Agent": (
16
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
17
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
18
+ "Chrome/120.0.0.0 Safari/537.36"
19
+ )
20
+ }
21
+
22
+ print(f"正在抓取 PTT Python 板最新文章: {URL} ...")
23
+
24
+ try:
25
+ response = requests.get(URL, headers=HEADERS, timeout=10)
26
+ response.raise_for_status()
27
+
28
+ soup = BeautifulSoup(response.text, "html.parser")
29
+
30
+ # === 【學員填空練習點 2:定位文章區塊】 ===
31
+ # TODO: 觀察網頁 HTML 結構,找出所有包裹文章資訊的 <div> 標籤(其 class 名稱為何?)
32
+ posts = soup.find_all("div", class_="r-ent")
33
+
34
+ print(f"\n成功取得 {len(posts)} 筆文章項目(含已刪除與置底公告):")
35
+ print("=" * 80)
36
+
37
+ for idx, post in enumerate(posts, 1):
38
+ # === 【學員填空練習點 3:提取文章標題與連結並防錯 (Null Check)】 ===
39
+ # TODO:
40
+ # 1. 尋找 class 為 "title" 的 <div> 標籤
41
+ # 2. 在該 <div> 底下尋找 <a> 標籤 (即文章連結)
42
+ # 3. ⚠️ 重要思考:若文章已刪除,將不會有 <a> 標籤。請撰寫條件判斷:
43
+ # - 若 <a> 標籤存在:提取其文字,並使用 urljoin 拼接 href 屬性得到完整網址。
44
+ # - 若 <a> 標籤不存在:直接將 title_div 的文字作為標題,並將網址設為 "無 (文章已刪除)"。
45
+ title_div = post.find("div", class_="title")
46
+ title_link = title_div.find("a") if title_div else None
47
+
48
+ if title_link:
49
+ title = title_link.get("title")
50
+ relative_url = title_link.get("href")
51
+ post_url = urljoin(URL, relative_url)
52
+ else:
53
+ # 文章已刪除
54
+ title_div = post.find("div", class_="title")
55
+ title = title_div.get_text().strip() if title_div else "(已刪除)"
56
+ post_url = "無 (文章已刪除)"
57
+
58
+
59
+ # === 【學員填空練習點 4:提取推文數】 ===
60
+ # TODO: 尋找 class 為 "nrec" 的 <div> 標籤。
61
+ # 技巧:使用 .get_text().strip() 取得文字。若無文字(0 推),請將變數預設為 "0"。
62
+ nrec_div = post.find("div", class_="nrec")
63
+ nrec = nrec_div.get_text().strip() if nrec_div else "0"
64
+
65
+ # === 【學員填空練習點 5:提取作者與發文日期】 ===
66
+ # TODO: 分別尋找 class 為 "author" 與 "date" 的 <div> 標籤,並提取純文字(記得使用 .strip() 去除空白)
67
+ author = post.find("div", class_="author").get_text().strip()
68
+ date = post.find("div", class_="date").get_text().strip()
69
+
70
+ # 輸出單篇文章資訊
71
+ print(f"[{idx}] 推文數: {nrec:3s} | 發文日期: {date} | 作者: {author}")
72
+ print(f" 標題: {title}")
73
+ print(f" 網址: {post_url}")
74
+ print("-" * 80)
75
+
76
+ except requests.exceptions.HTTPError as http_err:
77
+ print(f"[錯誤] PTT 伺服器拒絕或找不到網頁:{http_err}")
78
+ except requests.exceptions.RequestException as e:
79
+ print(f"[錯誤] 網路請求失敗:{e}")
80
+ except Exception as ex:
81
+ print(f"[錯誤] 解析資料時發生非預期錯誤:{ex}")
projects/2026_AI營1/劉宗穎/爬蟲分析_PTT/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Telearn Arcade Hub 入口檔(自動產生)
4
+ 實際執行: d4_1_ex2_ptt.py
5
+ 規格: projects/{班級}/{學生}/{專案}/main.py
6
+ """
7
+ from pathlib import Path
8
+ import os
9
+ import runpy
10
+ import sys
11
+
12
+ _ROOT = Path(__file__).resolve().parent
13
+ os.chdir(_ROOT)
14
+ if str(_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_ROOT))
16
+
17
+ # 執行原始學員腳本
18
+ runpy.run_path(str(_ROOT / 'd4_1_ex2_ptt.py'), run_name="__main__")
projects/2026_AI營1/劉宗穎/爬蟲分析_books/d4_1_ex1_books.py ADDED
@@ -0,0 +1,94 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # d4_1_ex1_books.py — Day 4 學員填空
2
+ # cd examples/Day4/student && python d4_1_ex1_books.py
3
+ # 參考答案:../d4_1_ex1_books.py
4
+
5
+ import requests
6
+ from bs4 import BeautifulSoup
7
+ from urllib.parse import urljoin
8
+
9
+ # 目標網站:專供爬蟲練習的圖書商城沙盒
10
+ URL = "http://books.toscrape.com/"
11
+
12
+ # === 【學員填空練習點 1:模擬瀏覽器 Header】 ===
13
+ # TODO: 建立 headers 字典,填入 "User-Agent" 來模擬真實瀏覽器(降低被簡易防爬機制拒絕的機率)
14
+ HEADERS = {
15
+ "User-Agent": (
16
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
17
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
18
+ "Chrome/120.0.0.0 Safari/537.36"
19
+ )
20
+ }
21
+
22
+ print(f"正在連線至圖書沙盒網站: {URL} ...")
23
+
24
+ try:
25
+ # 發送 GET 請求取得網頁 HTML
26
+ response = requests.get(URL, headers=HEADERS, timeout=10)
27
+
28
+ # 檢查 HTTP 狀態碼是否為 200 (OK),非 200 時引發 HTTPError 異常
29
+ response.raise_for_status()
30
+
31
+ # 解決編碼問題:強制將解碼設定為 utf-8 (避免 requests 預設 ISO-8859-1 解碼導致英鎊符號亂碼且在 Windows 印出時崩潰)
32
+ response.encoding = response.apparent_encoding
33
+
34
+ print("連線成功!開始解析書籍資料...\n")
35
+
36
+ # 初始化 BeautifulSoup 解析器
37
+ soup = BeautifulSoup(response.text, "html.parser")
38
+
39
+ # === 【學員填空練習點 2:定位 HTML 標籤區塊】 ===
40
+ # TODO: 找出網頁中所有書籍的容器。每本書都被包在一個 <article> 標籤中,且該標籤的 class 固定為 "product_pod"
41
+ books = soup.find_all("article", class_="product_pod")
42
+
43
+ print(f"成功在本頁找到 {len(books)} 本書籍:")
44
+ print("=" * 70)
45
+
46
+ for idx, book in enumerate(books, 1):
47
+ # === 【學員填空練習點 3:提取書籍名稱與連結】 ===
48
+ # TODO:
49
+ # 1. 尋找 <h3> 底下的 <a> 標籤
50
+ # 2. 取得 <a> 的 "title" 屬性(因為畫面上長度限制,.get_text() 可能會被 ellipsis 截斷)
51
+ # 3. 取得 <a> 的 "href" 屬性並使用 urljoin 拼接成完整網址
52
+ title_link = book.find("h3").find("a")
53
+ title = title_link.get("title") # 取得屬性 'title' 的值,避免拿到被截斷的文字
54
+
55
+ relative_url = title_link.get("href")
56
+ detail_url = urljoin(URL, relative_url)
57
+
58
+ # === 【學員填空練習點 4:提取書籍價格】 ===
59
+ # 提示:為了避免 Windows (CP950) 終端機不支援英鎊符號 '£' 而編碼崩潰,請加上 .replace("£", "GBP") 將其替換
60
+ # 觀察:價格存放在 class 為 "price_color" 的 <p> 標籤中,格式如 "£51.77"
61
+ # 技巧:將英鎊符號 '£' 替換為 'GBP',避免在 Windows (CP950) 終端機印出時因不支援該字元而崩潰
62
+ price_text = book.find("p", class_="price_color").get_text().replace("£", "GBP")
63
+
64
+ # === 【學員填空練習點 5:提取評價星等 (Rating)】 ===
65
+ # TODO:
66
+ # 1. 尋找 class 為 "star-rating" 的 <p> 標籤
67
+ # 2. 取得該標籤的 "class" 屬性值(這會是一個串列,如 ['star-rating', 'Three'])
68
+ # 3. 提取該串列的第二個元素作為星等字串 (若存在)
69
+
70
+ rating_tag = book.find("p", class_="star-rating")
71
+ rating_classes = rating_tag.get("class")
72
+ rating = "無"
73
+ if len(rating_classes) > 1:
74
+ rating = rating_classes[1] # 提取例如 'Three' (代表三顆星)
75
+
76
+ # === 【學員填空練習點 6:提取庫存狀態】 ===
77
+ # TODO: 尋找 class 為 "instock availability" 的 <p> 標籤,取得文字後去除首尾空白 (.strip())
78
+ stock_text = book.find("p", class_="instock availability").get_text().strip()
79
+
80
+ # 印出解析結果
81
+ print(f"【編號 {idx}】")
82
+ print(f"[書名] {title}")
83
+ print(f"[價格] {price_text}")
84
+ print(f"[評價] {rating} 星")
85
+ print(f"[狀態] {stock_text}")
86
+ print(f"[連結] {detail_url}")
87
+ print("-" * 70)
88
+
89
+ except requests.exceptions.HTTPError as http_err:
90
+ print(f"[錯誤] HTTP 連線異常:{http_err}")
91
+ except requests.exceptions.RequestException as e:
92
+ print(f"[錯誤] 網路請求失敗:{e}")
93
+ except Exception as ex:
94
+ print(f"[錯誤] 解析資料時發生非預期錯誤:{ex}")
projects/2026_AI營1/劉宗穎/爬蟲分析_books/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Telearn Arcade Hub 入口檔(自動產生)
4
+ 實際執行: d4_1_ex1_books.py
5
+ 規格: projects/{班級}/{學生}/{專案}/main.py
6
+ """
7
+ from pathlib import Path
8
+ import os
9
+ import runpy
10
+ import sys
11
+
12
+ _ROOT = Path(__file__).resolve().parent
13
+ os.chdir(_ROOT)
14
+ if str(_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_ROOT))
16
+
17
+ # 執行原始學員腳本
18
+ runpy.run_path(str(_ROOT / 'd4_1_ex1_books.py'), run_name="__main__")
projects/2026_AI營1/劉宗穎/爬蟲分析_ithelp/d4_1_ex3_ithelp.py ADDED
@@ -0,0 +1,98 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # d4_1_ex3_ithelp.py — Day 4 學員填空
2
+ # cd examples/Day4/student && python d4_1_ex3_ithelp.py
3
+ # 參考答案:../d4_1_ex3_ithelp.py
4
+
5
+ import requests
6
+ from bs4 import BeautifulSoup
7
+
8
+ # 目標網站:iT 邦幫忙技術文章首頁
9
+ URL = "https://ithelp.ithome.com.tw/articles?tab=tech"
10
+
11
+ # === 【學員填空練習點 1:模擬瀏覽器 Header】 ===
12
+ # TODO: 填入瀏覽器的 User-Agent,否則可能被伺服器拒絕連線
13
+ HEADERS = {
14
+ "User-Agent": (
15
+ "Mozilla/5.0 (Windows NT 10.0; Win64; x64) "
16
+ "AppleWebKit/537.36 (KHTML, like Gecko) "
17
+ "Chrome/120.0.0.0 Safari/537.36"
18
+ )
19
+ }
20
+
21
+ print(f"正在連線至 iT 邦幫忙並抓取技術文章: {URL} ...")
22
+
23
+ try:
24
+ response = requests.get(URL, headers=HEADERS, timeout=10)
25
+ response.raise_for_status()
26
+
27
+ print("連線成功!解析文章列表中...\n")
28
+ soup = BeautifulSoup(response.text, "html.parser")
29
+
30
+ # === 【學員填空練習點 2:定位文章卡片區塊】 ===
31
+ # TODO: 尋找所有包裹文章資訊的 <div> 標籤(其 class 為 "qa-list")
32
+ articles = soup.find_all("div", class_="qa-list")
33
+
34
+ print(f"成功取得前 {len(articles)} 篇熱門技術文章:")
35
+ print("=" * 80)
36
+
37
+ for idx, art in enumerate(articles, 1):
38
+ # === 【學員填空練習點 3:提取標題與超連結】 ===
39
+ # TODO:
40
+ # 1. 在 art 中尋找 class 為 "qa-list__title-link" 的 <a> 標籤
41
+ # 2. 提取其純文字內容並去除首尾空白 (.strip()),作為標題
42
+ # 3. 提取其 "href" 屬性,作為文章連結
43
+ title_link = art.find("a", class_="qa-list__title-link")
44
+ if not title_link:
45
+ continue
46
+ title = title_link.get_text().strip()
47
+ post_url = title_link.get("href").strip()
48
+
49
+ # === 【學員填空練習點 4:提取作者名稱】 ===
50
+ # TODO:
51
+ # 1. 尋找 class 為 "qa-list__info" 的 <div> 標籤
52
+ # 2. 在該 <div> 下尋找 class 為 "qa-list__info-link" 的 <a> 標籤
53
+ # 3. 提取其文字內容作為作者名稱
54
+ info_div = art.find("div", class_="qa-list__info")
55
+ author = "未知作者"
56
+ if info_div:
57
+ author_link = info_div.find("a", class_="qa-list__info-link")
58
+ if author_link:
59
+ author = author_link.get_text().strip()
60
+
61
+ # === 【學員填空練習點 5:提取發文時間】 ===
62
+ # TODO: 尋找 class 為 "qa-list__info-time" 的 <a> 標籤,並提取其 "title" 屬性值(因 title 屬性中包含完整時間秒數)
63
+ time_link = art.find("div", class_="qa-list__info-time")
64
+ post_time = time_link.get("title") if time_link else "未知時間"
65
+
66
+ # === 【學員填空練習點 6:利用 List 索引提取多項數據】 ===
67
+ # TODO:
68
+ # 1. 使用 find_all 找出卡片中所有 class 為 "qa-condition" 的 <a> 標籤
69
+ # 2. 當抓取到的數量大於等於 3 時,利用 List 索引取得對應的資訊:
70
+ # - 索引 0 的項目底下的 "qa-condition__count" <span>:Like 數
71
+ # - 索引 1 的項目底下的 "qa-condition__count" <span>:回應數
72
+ # - 索引 2 的項目底下的 "qa-condition__count" <span>:瀏覽數
73
+ conditions = art.find_all("a", class_="qa-condition")
74
+
75
+ likes = "0"
76
+ comments = "0"
77
+ views = "0"
78
+
79
+ if len(conditions) >= 3:
80
+ # 請在此處填入利用 conditions[0]、conditions[1]、conditions[2] 尋找 span 的代碼
81
+ likes = conditions[0].find("span", class_="qa-condition__count").get_text().strip()
82
+ comments = conditions[1].find("span", class_="qa-condition__count").get_text().strip()
83
+ views = conditions[2].find("span", class_="qa-condition__count").get_text().strip()
84
+
85
+ # 格式化輸出
86
+ print(f"【排名 {idx}】")
87
+ print(f"[標題] {title}")
88
+ print(f"[作者] {author} | [時間] {post_time}")
89
+ print(f"[數據] Like: {likes} | 回應: {comments} | 瀏覽/訂閱: {views}")
90
+ print(f"[連結] {post_url}")
91
+ print("-" * 80)
92
+
93
+ except requests.exceptions.HTTPError as http_err:
94
+ print(f"[錯誤] 伺服器回應異常:{http_err}")
95
+ except requests.exceptions.RequestException as e:
96
+ print(f"[錯誤] 網路請求失敗:{e}")
97
+ except Exception as ex:
98
+ print(f"[錯誤] 解析資料時發生非預期錯誤:{ex}")
projects/2026_AI營1/劉宗穎/爬蟲分析_ithelp/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Telearn Arcade Hub 入口檔(自動產生)
4
+ 實際執行: d4_1_ex3_ithelp.py
5
+ 規格: projects/{班級}/{學生}/{專案}/main.py
6
+ """
7
+ from pathlib import Path
8
+ import os
9
+ import runpy
10
+ import sys
11
+
12
+ _ROOT = Path(__file__).resolve().parent
13
+ os.chdir(_ROOT)
14
+ if str(_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_ROOT))
16
+
17
+ # 執行原始學員腳本
18
+ runpy.run_path(str(_ROOT / 'd4_1_ex3_ithelp.py'), run_name="__main__")
projects/2026_AI營1/劉宗穎/{爬虫分析与AI报告 → 爬蟲分析與AI報告}/d4_1_web_scraping.py RENAMED
File without changes
projects/2026_AI營1/劉宗穎/{爬虫分析与AI报告 → 爬蟲分析與AI報告}/d4_2_agent_task_chain.py RENAMED
@@ -7,8 +7,39 @@ import os
7
  import requests
8
  from bs4 import BeautifulSoup
9
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
10
  GEMINI_MODEL = os.environ.get("GEMINI_MODEL", "gemini-3.1-flash-lite")
11
- api_key = os.environ.get("GEMINI_API_KEY", "AQ.Ab8RN6JvbUqLW-wcddeMducmta0aGE2QtcrmF4Kfd8nnpZ9mzg")
12
 
13
  HEADERS = {
14
  "User-Agent": (
@@ -96,7 +127,7 @@ def run_task_chain(topic):
96
  f"- 模擬數據:\"Love is all, love is new.\" -- John Lennon\n\n"
97
  f"## 二、 核心思想分析\n"
98
  f"本機模擬分析指出,此主題的語錄主要著眼於人與人之間的連結與心靈啟發。\n"
99
- f"*(注意:此為 Mock 離線生成報告,設定 GEMINI_API_KEY 以啟用 Gemini 真實生成。)*\n"
100
  )
101
  else:
102
  print(f"\n[步驟 2] 正在調用 Gemini ({GEMINI_MODEL}) 進行深度分析與排版...")
 
7
  import requests
8
  from bs4 import BeautifulSoup
9
 
10
+
11
+ def _telearn_require_gemini_api_key(*, allow_skip: bool = False) -> str:
12
+ """終端機輸入介面:請使用者自行貼上 GEMINI_API_KEY。"""
13
+ import os
14
+ key = (os.environ.get("GEMINI_API_KEY") or "").strip()
15
+ if key:
16
+ print("[Telearn] 已使用環境變數中的 GEMINI_API_KEY")
17
+ return key
18
+ print()
19
+ print("=" * 54)
20
+ print(" 本專案需要 Google Gemini API Key")
21
+ print(" 取得金鑰:https://aistudio.google.com/apikey")
22
+ print(" 請在下方貼上 Key 後按 Enter")
23
+ print(" (僅本次執行使用,不會寫入任何檔案)")
24
+ if allow_skip:
25
+ print(" 若要改用離線 Mock 模式:直接按 Enter 略過")
26
+ print("=" * 54)
27
+ try:
28
+ key = input("請輸入 GEMINI_API_KEY: ").strip()
29
+ except EOFError:
30
+ key = ""
31
+ if key:
32
+ os.environ["GEMINI_API_KEY"] = key
33
+ print("[Telearn] 已接收 API Key,開始連線…")
34
+ return key
35
+ if allow_skip:
36
+ print("[Telearn] 未輸入 Key → 使用離線 Mock / 略過雲端功能")
37
+ return ""
38
+ print("[結束] 未輸入 API Key,無法繼續執行 Gemini 功能。")
39
+ raise SystemExit(0)
40
+
41
  GEMINI_MODEL = os.environ.get("GEMINI_MODEL", "gemini-3.1-flash-lite")
42
+ api_key = _telearn_require_gemini_api_key(allow_skip=True)
43
 
44
  HEADERS = {
45
  "User-Agent": (
 
127
  f"- 模擬數據:\"Love is all, love is new.\" -- John Lennon\n\n"
128
  f"## 二、 核心思想分析\n"
129
  f"本機模擬分析指出,此主題的語錄主要著眼於人與人之間的連結與心靈啟發。\n"
130
+ f"*(注意:此為 Mock 離線生成報告,於啟動時輸入 GEMINI_API_KEY 以啟用 Gemini 真實生成。)*\n"
131
  )
132
  else:
133
  print(f"\n[步驟 2] 正在調用 Gemini ({GEMINI_MODEL}) 進行深度分析與排版...")
projects/2026_AI營1/劉宗穎/{爬虫分析与AI报告 → 爬蟲分析與AI報告}/d4_2_ex2_crawler_ai.py RENAMED
@@ -9,8 +9,39 @@ from urllib.parse import urljoin
9
  from google import genai
10
 
11
  # 設定預設使用的 Gemini 模型
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
12
  GEMINI_MODEL = os.environ.get("GEMINI_MODEL", "gemini-3.1-flash-lite")
13
- api_key = os.environ.get("GEMINI_API_KEY", "AQ.Ab8RN6JvbUqLW-wcddeMducmta0aGE2QtcrmF4Kfd8nnpZ9mzg")
14
 
15
  HEADERS = {
16
  "User-Agent": (
@@ -176,7 +207,7 @@ def run_analysis_chain(source_type):
176
 
177
  # 步驟 2: AI 分析 (或 Mock 降級防禦)
178
  if not api_key:
179
- print("\n[步驟 2] 未偵測到環境變數 GEMINI_API_KEY,自動啟動 Mock 離線降級機制...")
180
  # === 【學員填空練習點 5:Mock 降級防衛機制】 ===
181
  # TODO 5: 請設計一段模擬的 Markdown 格式分析報告字串,指派給 report_content。
182
  # 報告內容至少需要包含標題與數據摘要,避免程式在無 Key 時報錯中斷。
 
9
  from google import genai
10
 
11
  # 設定預設使用的 Gemini 模型
12
+
13
+ def _telearn_require_gemini_api_key(*, allow_skip: bool = False) -> str:
14
+ """終端機輸入介面:請使用者自行貼上 GEMINI_API_KEY。"""
15
+ import os
16
+ key = (os.environ.get("GEMINI_API_KEY") or "").strip()
17
+ if key:
18
+ print("[Telearn] 已使用環境變數中的 GEMINI_API_KEY")
19
+ return key
20
+ print()
21
+ print("=" * 54)
22
+ print(" 本專案需要 Google Gemini API Key")
23
+ print(" 取得金鑰:https://aistudio.google.com/apikey")
24
+ print(" 請在下方貼上 Key 後按 Enter")
25
+ print(" (僅本次執行使用,不會寫入任何檔案)")
26
+ if allow_skip:
27
+ print(" 若要改用離線 Mock 模式:直接按 Enter 略過")
28
+ print("=" * 54)
29
+ try:
30
+ key = input("請輸入 GEMINI_API_KEY: ").strip()
31
+ except EOFError:
32
+ key = ""
33
+ if key:
34
+ os.environ["GEMINI_API_KEY"] = key
35
+ print("[Telearn] 已接收 API Key,開始連線…")
36
+ return key
37
+ if allow_skip:
38
+ print("[Telearn] 未輸入 Key → 使用離線 Mock / 略過雲端功能")
39
+ return ""
40
+ print("[結束] 未輸入 API Key,無法繼續執行 Gemini 功能。")
41
+ raise SystemExit(0)
42
+
43
  GEMINI_MODEL = os.environ.get("GEMINI_MODEL", "gemini-3.1-flash-lite")
44
+ api_key = _telearn_require_gemini_api_key(allow_skip=True)
45
 
46
  HEADERS = {
47
  "User-Agent": (
 
207
 
208
  # 步驟 2: AI 分析 (或 Mock 降級防禦)
209
  if not api_key:
210
+ print("\n[步驟 2] 未輸入 GEMINI_API_KEY,啟動 Mock 離線降級機制...")
211
  # === 【學員填空練習點 5:Mock 降級防衛機制】 ===
212
  # TODO 5: 請設計一段模擬的 Markdown 格式分析報告字串,指派給 report_content。
213
  # 報告內容至少需要包含標題與數據摘要,避免程式在無 Key 時報錯中斷。
projects/2026_AI營1/劉宗穎/爬蟲分析與AI報告/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Telearn Arcade Hub 入口檔(自動產生)
4
+ 實際執行: d4_2_ex2_crawler_ai.py
5
+ 規格: projects/{班級}/{學生}/{專案}/main.py
6
+ """
7
+ from pathlib import Path
8
+ import os
9
+ import runpy
10
+ import sys
11
+
12
+ _ROOT = Path(__file__).resolve().parent
13
+ os.chdir(_ROOT)
14
+ if str(_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_ROOT))
16
+
17
+ # 執行原始學員腳本
18
+ runpy.run_path(str(_ROOT / 'd4_2_ex2_crawler_ai.py'), run_name="__main__")
projects/2026_AI營1/劉宗穎/眼睛距離偵測/main.py ADDED
@@ -0,0 +1,18 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # -*- coding: utf-8 -*-
2
+ """
3
+ Telearn Arcade Hub 入口檔(自動產生)
4
+ 實際執行: step0_eyeguard_template.py
5
+ 規格: projects/{班級}/{學生}/{專案}/main.py
6
+ """
7
+ from pathlib import Path
8
+ import os
9
+ import runpy
10
+ import sys
11
+
12
+ _ROOT = Path(__file__).resolve().parent
13
+ os.chdir(_ROOT)
14
+ if str(_ROOT) not in sys.path:
15
+ sys.path.insert(0, str(_ROOT))
16
+
17
+ # 執行原始學員腳本
18
+ runpy.run_path(str(_ROOT / 'step0_eyeguard_template.py'), run_name="__main__")