youngtsai commited on
Commit
0a7d39a
·
1 Parent(s): a7f852c
app.py CHANGED
@@ -27,52 +27,41 @@ from googleapiclient.http import MediaIoBaseDownload
27
  from googleapiclient.http import MediaIoBaseUpload
28
 
29
  from educational_material import EducationalMaterial
 
30
 
31
 
32
 
33
 
 
 
34
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
35
 
36
-
37
-
38
- # 假设您的环境变量或Secret的名称是GOOGLE_APPLICATION_CREDENTIALS_JSON
39
- # credentials_json_string = os.getenv("GOOGLE_APPLICATION_CREDENTIALS_JSON")
40
- # credentials_dict = json.loads(credentials_json_string)
41
- # SCOPES = ['https://www.googleapis.com/auth/drive']
42
- # credentials = service_account.Credentials.from_service_account_info(
43
- # credentials_dict, scopes=SCOPES)
44
- # service = build('drive', 'v3', credentials=credentials)
45
- # # 列出 Google Drive 上的前10個文件
46
- # results = service.files().list(pageSize=10, fields="nextPageToken, files(id, name)").execute()
47
- # items = results.get('files', [])
48
-
49
- # if not items:
50
- # print('No files found.')
51
- # else:
52
- # print("=====Google Drive 上的前10個文件=====")
53
- # print('Files:')
54
- # for item in items:
55
- # print(u'{0} ({1})'.format(item['name'], item['id']))
56
-
57
-
58
-
59
- OUTPUT_PATH = 'videos'
60
  TRANSCRIPTS = []
61
  CURRENT_INDEX = 0
62
  VIDEO_ID = ""
63
 
64
- PASSWORD = os.getenv("PASSWORD")
65
-
66
- OPEN_AI_KEY = os.getenv("OPEN_AI_KEY")
67
  OPEN_AI_CLIENT = OpenAI(api_key=OPEN_AI_KEY)
68
-
69
- GROQ_API_KEY = os.getenv("GROQ_API_KEY")
70
  GROQ_CLIENT = Groq(api_key=GROQ_API_KEY)
71
-
72
- JUTOR_CHAT_KEY = os.getenv("JUTOR_CHAT_KEY")
73
-
74
- DRIVE_KEY = os.getenv("GOOGLE_APPLICATION_CREDENTIALS_JSON")
75
- GCS_KEY = os.getenv("GOOGLE_APPLICATION_CREDENTIALS_JSON")
76
 
77
  # 驗證 password
78
  def verify_password(password):
@@ -82,30 +71,6 @@ def verify_password(password):
82
  raise gr.Error("密碼錯誤")
83
 
84
  # ====gcs====
85
- def init_gcs_client(service_account_key_string):
86
- """使用服务账号密钥文件创建 GCS 客户端"""
87
- credentials_json_string = service_account_key_string
88
- credentials_dict = json.loads(credentials_json_string)
89
- credentials = service_account.Credentials.from_service_account_info(credentials_dict)
90
- gcs_client = storage.Client(credentials=credentials, project=credentials_dict['project_id'])
91
- return gcs_client
92
-
93
- def gcs_create_bucket_folder_if_not_exists(gcs_client, bucket_name, folder_name):
94
- """检查是否存在特定名称的文件夹(前缀),如果不存在则创建一个标记文件来模拟文件夹"""
95
- bucket = gcs_client.bucket(bucket_name)
96
- blob = bucket.blob(folder_name)
97
- if not blob.exists():
98
- blob.upload_from_string('', content_type='application/x-www-form-urlencoded;charset=UTF-8')
99
- print(f"GCS Folder '{folder_name}' created.")
100
- else:
101
- print(f"GCS Folder '{folder_name}' already exists.")
102
-
103
- def gcs_check_folder_exists(gcs_client, bucket_name, folder_name):
104
- """检查 GCS 存储桶中是否存在指定的文件夹"""
105
- bucket = gcs_client.bucket(bucket_name)
106
- blobs = list(bucket.list_blobs(prefix=folder_name))
107
- return len(blobs) > 0
108
-
109
  def gcs_check_file_exists(gcs_client, bucket_name, file_name):
110
  """
111
  检查 GCS 存储桶中是否存在指定的文件
@@ -388,6 +353,7 @@ def get_transcript(video_id):
388
  def generate_transcription(video_id):
389
  youtube_url = f'https://www.youtube.com/watch?v={video_id}'
390
  codec_name = "mp3"
 
391
  ydl_opts = {
392
  'format': 'bestaudio/best',
393
  'postprocessors': [{
@@ -395,14 +361,13 @@ def generate_transcription(video_id):
395
  'preferredcodec': codec_name,
396
  'preferredquality': '192'
397
  }],
398
- 'outtmpl': f'{video_id}.%(ext)s',
399
  }
400
  print("===download video mp3===")
401
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
402
  ydl.download([youtube_url])
403
 
404
- audio_path = f'{video_id}.{codec_name}'
405
-
406
  print("===transcription by open ai===")
407
  with open(audio_path, "rb") as audio_file:
408
  srt_content = OPEN_AI_CLIENT.audio.transcriptions.create(
@@ -410,7 +375,7 @@ def generate_transcription(video_id):
410
  file=audio_file,
411
  response_format="verbose_json",
412
  timestamp_granularities=["segment"],
413
- prompt="Transcribe the following audio file. if chinese, please using 'language: zh-TW' in the prompt.",
414
  )
415
 
416
  # get segments
@@ -467,39 +432,18 @@ def process_transcript_and_screenshots(video_id):
467
  updated_transcript_text = json.dumps(transcript, ensure_ascii=False, indent=2)
468
  update_file_on_drive(service, file_id, updated_transcript_text)
469
  print("逐字稿已更新,包括截图链接")
470
-
471
- # init gcs client
472
- gcs_client = init_gcs_client(GCS_KEY)
473
- bucket_name = 'video_ai_assistant'
474
- # 检查 folder 是否存在
475
- is_gcs_exists = gcs_check_folder_exists(gcs_client, bucket_name, video_id)
476
- if not is_gcs_exists:
477
- gcs_create_bucket_folder_if_not_exists(gcs_client, bucket_name, video_id)
478
- copy_all_files_from_drive_to_gcs(service, gcs_client, folder_id, bucket_name, video_id)
479
- print("Drive file 已上传到GCS")
480
- else:
481
- print("GCS folder:{video_id} 已存在")
482
-
483
  return transcript
484
 
485
  def process_transcript_and_screenshots_on_gcs(video_id):
486
  print("====process_transcript_and_screenshots_on_gcs====")
487
  # GCS
488
- gcs_client = init_gcs_client(GCS_KEY)
489
  bucket_name = 'video_ai_assistant'
490
- # 检查 folder 是否存在
491
- # is_gcs_exists = gcs_check_folder_exists(gcs_client, bucket_name, video_id)
492
- # if not is_gcs_exists:
493
- # gcs_create_bucket_folder_if_not_exists(gcs_client, bucket_name, video_id)
494
- # print("GCS folder:{video_id} 已创建")
495
- # else:
496
- # print("GCS folder:{video_id} 已存在")
497
-
498
  # 逐字稿文件名
499
  transcript_file_name = f'{video_id}_transcript.json'
500
  transcript_blob_name = f"{video_id}/{transcript_file_name}"
501
  # 检查逐字稿是否存在
502
- is_transcript_exists = gcs_check_file_exists(gcs_client, bucket_name, transcript_blob_name)
503
  if not is_transcript_exists:
504
  # 从YouTube获取逐字稿并上传
505
  try:
@@ -701,12 +645,12 @@ def process_web_link(link):
701
  def get_reading_passage(video_id, df_string, source):
702
  if source == "gcs":
703
  print("===get_reading_passage on gcs===")
704
- gcs_client = init_gcs_client(GCS_KEY)
705
  bucket_name = 'video_ai_assistant'
706
  file_name = f'{video_id}_reading_passage.json'
707
  blob_name = f"{video_id}/{file_name}"
708
  # 检查 reading_passage 是否存在
709
- is_file_exists = gcs_check_file_exists(gcs_client, bucket_name, blob_name)
710
  if not is_file_exists:
711
  reading_passage = generate_reading_passage(df_string)
712
  reading_passage_json = {"reading_passage": str(reading_passage)}
@@ -749,7 +693,8 @@ def generate_reading_passage(df_string):
749
  文本自行判斷資料的種類
750
  幫我組合成 Reading Passage
751
  並潤稿讓文句通順
752
- 請一定要使用 zh-TW
 
753
  """
754
  messages = [
755
  {"role": "system", "content": sys_content},
@@ -773,12 +718,12 @@ def generate_reading_passage(df_string):
773
  def get_mind_map(video_id, df_string, source):
774
  if source == "gcs":
775
  print("===get_mind_map on gcs===")
776
- gcs_client = init_gcs_client(GCS_KEY)
777
  bucket_name = 'video_ai_assistant'
778
  file_name = f'{video_id}_mind_map.json'
779
  blob_name = f"{video_id}/{file_name}"
780
  # 检查檔案是否存在
781
- is_file_exists = gcs_check_file_exists(gcs_client, bucket_name, blob_name)
782
  if not is_file_exists:
783
  mind_map = generate_mind_map(df_string)
784
  mind_map_json = {"mind_map": str(mind_map)}
@@ -856,12 +801,12 @@ def get_mind_map_html(mind_map):
856
  def get_video_id_summary(video_id, df_string, source):
857
  if source == "gcs":
858
  print("===get_video_id_summary on gcs===")
859
- gcs_client = init_gcs_client(GCS_KEY)
860
  bucket_name = 'video_ai_assistant'
861
  file_name = f'{video_id}_summary.json'
862
  summary_file_blob_name = f"{video_id}/{file_name}"
863
  # 检查 summary_file 是否存在
864
- is_summary_file_exists = gcs_check_file_exists(gcs_client, bucket_name, summary_file_blob_name)
865
  if not is_summary_file_exists:
866
  summary = generate_summarise(df_string)
867
  summary_json = {"summary": str(summary)}
@@ -988,12 +933,12 @@ def get_questions(video_id, df_string, source="gcs"):
988
  if source == "gcs":
989
  # 去 gcs 確認是有有 video_id_questions.json
990
  print("===get_questions on gcs===")
991
- gcs_client = init_gcs_client(GCS_KEY)
992
  bucket_name = 'video_ai_assistant'
993
  file_name = f'{video_id}_questions.json'
994
  blob_name = f"{video_id}/{file_name}"
995
  # 检查檔案是否存在
996
- is_questions_exists = gcs_check_file_exists(gcs_client, bucket_name, blob_name)
997
  if not is_questions_exists:
998
  questions = generate_questions(df_string)
999
  questions_text = json.dumps(questions, ensure_ascii=False, indent=2)
@@ -1675,9 +1620,9 @@ with gr.Blocks() as demo:
1675
  with gr.Accordion("prompt", open=False):
1676
  worksheet_prompt = gr.Textbox(label="worksheet_prompt", show_copy_button=True, lines=40)
1677
  with gr.Tab("課程計畫"):
1678
- lesson_plan_content_type_name = gr.Textbox(value="lesson_plan", visible=False)
1679
  lesson_plan_time = gr.Slider(label="選擇課程時間(分鐘)", minimum=10, maximum=120, step=5, value=40)
1680
- lesson_plan_btn = gr.Button("生成課程計畫")
1681
  with gr.Accordion("prompt", open=False):
1682
  lesson_plan_prompt = gr.Textbox(label="worksheet_prompt", show_copy_button=True, lines=40)
1683
 
 
27
  from googleapiclient.http import MediaIoBaseUpload
28
 
29
  from educational_material import EducationalMaterial
30
+ from storage_service import GoogleCloudStorage
31
 
32
 
33
 
34
 
35
+ is_env_local = os.getenv("IS_ENV_LOCAL", "false") == "true"
36
+ print(f"is_env_local: {is_env_local}")
37
 
38
+ if is_env_local:
39
+ with open("local_config.json") as f:
40
+ config = json.load(f)
41
+ PASSWORD = config["PASSWORD"]
42
+ GCS_KEY = json.dumps(config["GOOGLE_APPLICATION_CREDENTIALS_JSON"])
43
+ DRIVE_KEY = json.dumps(config["GOOGLE_APPLICATION_CREDENTIALS_JSON"])
44
+ OPEN_AI_KEY = config["OPEN_AI_KEY"]
45
+ GROQ_API_KEY = config["GROQ_API_KEY"]
46
+ JUTOR_CHAT_KEY = config["JUTOR_CHAT_KEY"]
47
+ OUTPUT_PATH = config["OUTPUT_PATH"]
48
+ else:
49
+ PASSWORD = os.getenv("PASSWORD")
50
+ GCS_KEY = os.getenv("GOOGLE_APPLICATION_CREDENTIALS_JSON")
51
+ DRIVE_KEY = os.getenv("GOOGLE_APPLICATION_CREDENTIALS_JSON")
52
+ OPEN_AI_KEY = os.getenv("OPEN_AI_KEY")
53
+ GROQ_API_KEY = os.getenv("GROQ_API_KEY")
54
+ JUTOR_CHAT_KEY = os.getenv("JUTOR_CHAT_KEY")
55
+ OUTPUT_PATH = 'videos'
56
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  TRANSCRIPTS = []
58
  CURRENT_INDEX = 0
59
  VIDEO_ID = ""
60
 
 
 
 
61
  OPEN_AI_CLIENT = OpenAI(api_key=OPEN_AI_KEY)
 
 
62
  GROQ_CLIENT = Groq(api_key=GROQ_API_KEY)
63
+ GCS_SERVICE = GoogleCloudStorage(GCS_KEY)
64
+ GCS_CLIENT = GCS_SERVICE.client
 
 
 
65
 
66
  # 驗證 password
67
  def verify_password(password):
 
71
  raise gr.Error("密碼錯誤")
72
 
73
  # ====gcs====
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
74
  def gcs_check_file_exists(gcs_client, bucket_name, file_name):
75
  """
76
  检查 GCS 存储桶中是否存在指定的文件
 
353
  def generate_transcription(video_id):
354
  youtube_url = f'https://www.youtube.com/watch?v={video_id}'
355
  codec_name = "mp3"
356
+ outtmpl = f"{OUTPUT_PATH}/{video_id}.%(ext)s"
357
  ydl_opts = {
358
  'format': 'bestaudio/best',
359
  'postprocessors': [{
 
361
  'preferredcodec': codec_name,
362
  'preferredquality': '192'
363
  }],
364
+ 'outtmpl': outtmpl,
365
  }
366
  print("===download video mp3===")
367
  with yt_dlp.YoutubeDL(ydl_opts) as ydl:
368
  ydl.download([youtube_url])
369
 
370
+ audio_path = f"{OUTPUT_PATH}/{video_id}.{codec_name}"
 
371
  print("===transcription by open ai===")
372
  with open(audio_path, "rb") as audio_file:
373
  srt_content = OPEN_AI_CLIENT.audio.transcriptions.create(
 
375
  file=audio_file,
376
  response_format="verbose_json",
377
  timestamp_granularities=["segment"],
378
+ prompt="如果影片有中文,請使用繁體中文,這對於我很重要"
379
  )
380
 
381
  # get segments
 
432
  updated_transcript_text = json.dumps(transcript, ensure_ascii=False, indent=2)
433
  update_file_on_drive(service, file_id, updated_transcript_text)
434
  print("逐字稿已更新,包括截图链接")
 
 
 
 
 
 
 
 
 
 
 
 
 
435
  return transcript
436
 
437
  def process_transcript_and_screenshots_on_gcs(video_id):
438
  print("====process_transcript_and_screenshots_on_gcs====")
439
  # GCS
440
+ gcs_client = GCS_CLIENT
441
  bucket_name = 'video_ai_assistant'
 
 
 
 
 
 
 
 
442
  # 逐字稿文件名
443
  transcript_file_name = f'{video_id}_transcript.json'
444
  transcript_blob_name = f"{video_id}/{transcript_file_name}"
445
  # 检查逐字稿是否存在
446
+ is_transcript_exists = GCS_SERVICE.check_file_exists(bucket_name, transcript_blob_name)
447
  if not is_transcript_exists:
448
  # 从YouTube获取逐字稿并上传
449
  try:
 
645
  def get_reading_passage(video_id, df_string, source):
646
  if source == "gcs":
647
  print("===get_reading_passage on gcs===")
648
+ gcs_client = GCS_CLIENT
649
  bucket_name = 'video_ai_assistant'
650
  file_name = f'{video_id}_reading_passage.json'
651
  blob_name = f"{video_id}/{file_name}"
652
  # 检查 reading_passage 是否存在
653
+ is_file_exists = GCS_SERVICE.check_file_exists(bucket_name, blob_name)
654
  if not is_file_exists:
655
  reading_passage = generate_reading_passage(df_string)
656
  reading_passage_json = {"reading_passage": str(reading_passage)}
 
693
  文本自行判斷資料的種類
694
  幫我組合成 Reading Passage
695
  並潤稿讓文句通順
696
+ 請一定要使用繁體中文 zh-TW,並用台灣人的口語
697
+ 產生的結果不要前後文解釋,只需要專注提供 Reading Passage
698
  """
699
  messages = [
700
  {"role": "system", "content": sys_content},
 
718
  def get_mind_map(video_id, df_string, source):
719
  if source == "gcs":
720
  print("===get_mind_map on gcs===")
721
+ gcs_client = GCS_CLIENT
722
  bucket_name = 'video_ai_assistant'
723
  file_name = f'{video_id}_mind_map.json'
724
  blob_name = f"{video_id}/{file_name}"
725
  # 检查檔案是否存在
726
+ is_file_exists = GCS_SERVICE.check_file_exists(bucket_name, blob_name)
727
  if not is_file_exists:
728
  mind_map = generate_mind_map(df_string)
729
  mind_map_json = {"mind_map": str(mind_map)}
 
801
  def get_video_id_summary(video_id, df_string, source):
802
  if source == "gcs":
803
  print("===get_video_id_summary on gcs===")
804
+ gcs_client = GCS_CLIENT
805
  bucket_name = 'video_ai_assistant'
806
  file_name = f'{video_id}_summary.json'
807
  summary_file_blob_name = f"{video_id}/{file_name}"
808
  # 检查 summary_file 是否存在
809
+ is_summary_file_exists = GCS_SERVICE.check_file_exists(bucket_name, summary_file_blob_name)
810
  if not is_summary_file_exists:
811
  summary = generate_summarise(df_string)
812
  summary_json = {"summary": str(summary)}
 
933
  if source == "gcs":
934
  # 去 gcs 確認是有有 video_id_questions.json
935
  print("===get_questions on gcs===")
936
+ gcs_client = GCS_CLIENT
937
  bucket_name = 'video_ai_assistant'
938
  file_name = f'{video_id}_questions.json'
939
  blob_name = f"{video_id}/{file_name}"
940
  # 检查檔案是否存在
941
+ is_questions_exists = GCS_SERVICE.check_file_exists(bucket_name, blob_name)
942
  if not is_questions_exists:
943
  questions = generate_questions(df_string)
944
  questions_text = json.dumps(questions, ensure_ascii=False, indent=2)
 
1620
  with gr.Accordion("prompt", open=False):
1621
  worksheet_prompt = gr.Textbox(label="worksheet_prompt", show_copy_button=True, lines=40)
1622
  with gr.Tab("課程計畫"):
1623
+ lesson_plan_content_type_name = gr.Textbox(value="lesson_plan", visible=False)
1624
  lesson_plan_time = gr.Slider(label="選擇課程時間(分鐘)", minimum=10, maximum=120, step=5, value=40)
1625
+ lesson_plan_btn = gr.Button("生成課程計畫 📕")
1626
  with gr.Accordion("prompt", open=False):
1627
  lesson_plan_prompt = gr.Textbox(label="worksheet_prompt", show_copy_button=True, lines=40)
1628
 
educational_material.py CHANGED
@@ -189,60 +189,141 @@ class EducationalMaterial:
189
  return worksheet_prompt
190
 
191
  def _generate_lesson_plan_prompt(self):
192
- lesson_plan_prompt = """
193
- 你是一位專業教師,
194
- 請根據以上要教學的項目細節
195
- 幫我安排一個 lesson plan
196
-
197
- 規則如下,請嚴格遵守
198
- 1. 請使用 zh-TW
199
- 2. 該換行就換行,盡量滿足 word .doc 的格式
200
- 3. 【課程大綱】的工作項目請嚴格遵守【課程時間】的時間長度,總和時間不要超過或不足
201
- 4. 時間安排盡量以五分鐘的倍數為一個單位
202
- 5. 並且根據課程目標安排教學內容
203
 
204
- 範例:
205
- 【課程主題】計算面積的創意設計
206
 
207
- 【課程目標】
208
- - 理解不同形狀(正方形、長方形、三角形)的面積計算方法。
209
- - 學會如何通過對摺改變形狀並計算新形狀的面積。
210
- - 鼓勵創造力和實際操作,將數學知識應用於創作中。
211
 
212
- 【年級】國小三年級
213
 
214
- 【難度】基礎
215
 
216
- 【課程時間】60 分鐘
217
 
218
- 【課程大綱】
219
- - 開場介紹 (5 分鐘)
220
- 簡短介紹今天的課程主題和目標。
221
 
222
- - 講解與示範 (15 分鐘)
223
- 形狀與面積:介紹基本幾何形狀(正方形、長方形、三角形)的面積計算公式。
224
- 對摺技巧:示範如何通過對摺正方形貼紙創造新的形狀(半正方形、三角形)並解釋面積如何改變。
225
- 應用討論:討論如何使用這些形狀創造不同的圖案或設計,特別是斜屋頂的設計。
226
 
227
- - 實作活動 (25 分鐘)
228
- 材料準備:每位學生發放正方形貼紙、剪刀、膠水和紙張。
229
- 創意剪貼:學生使用對摺技巧製作不同的幾何形狀,並將它們組合成一個有關斜屋頂的圖案或其他創意設計。
230
- 面積計算:學生計算他們設計中每種形狀的面積,以及整體設計的總面積。
231
-
232
- - 分享與討論 (10 分鐘)
233
- 學生展示他們的創作並分享他們的設計過程,包括面積如何影響他們的設計選擇。
234
- 討論學生在創作過程中遇到的挑戰和他們如何解決。
235
 
236
- - 總結 (5 分鐘)
237
- 回顧課程學到的主要概念。
238
- 鼓勵學生在家中嘗試更多的創意組合和面積計算。
239
 
240
- - 課後活動
241
- 建議學生使用家中的材料(如報紙、雜誌等)進行更多的創意剪貼,並嘗試計算不同形狀的面積,進一步鞏固今天學到的知識。
242
 
243
- 【教學策略總結】
244
- 通過這個課程計劃,學生不僅能夠學習數學知識,還能夠將這些知識應用於實際生活中,從而增強他們的創造力和問題解決能力。
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
245
  """
 
246
 
247
  return lesson_plan_prompt
248
 
 
189
  return worksheet_prompt
190
 
191
  def _generate_lesson_plan_prompt(self):
192
+ # lesson_plan_prompt = """
193
+ # 你是一位專業教師,
194
+ # 請根據以上要教學的項目細節
195
+ # 幫我安排一個 lesson plan
196
+
197
+ # 規則如下,請嚴格遵守
198
+ # 1. 請使用 zh-TW
199
+ # 2. 該換行就換行,盡量滿足 word .doc 的格式
200
+ # 3. 【課程大綱】的工作項目請嚴格遵守【課程時間】的時間長度,總和時間不要超過或不足
201
+ # 4. 時間安排盡量以五分鐘的倍數為一個單位
202
+ # 5. 並且根據課程目標安排教學內容
203
 
204
+ # 範例:
205
+ # 【課程主題】計算面積的創意設計
206
 
207
+ # 【課程目標】
208
+ # - 理解不同形狀(正方形、長方形、三角形)的面積計算方法。
209
+ # - 學會如何通過對摺改變形狀並計算新形狀的面積。
210
+ # - 鼓勵創造力和實際操作,將數學知識應用於創作中。
211
 
212
+ # 【年級】國小三年級
213
 
214
+ # 【難度】基礎
215
 
216
+ # 【課程時間】60 分鐘
217
 
218
+ # 【課程大綱】
219
+ # - 開場介紹 (5 分鐘)
220
+ # 簡短介紹今天的課程主題和目標。
221
 
222
+ # - 講解與示範 (15 分鐘)
223
+ # 形狀與面積:介紹基本幾何形狀(正方形、長方形、三角形)的面積計算公式。
224
+ # 對摺技巧:示範如何通過對摺正方形貼紙創造新的形狀(半正方形、三角形)並解釋面積如何改變。
225
+ # 應用討論:討論如何使用這些形狀創造不同的圖案或設計,特別是斜屋頂的設計。
226
 
227
+ # - 實作活動 (25 分鐘)
228
+ # 材料準備:每位學生發放正方形貼紙、剪刀、膠水和紙張。
229
+ # 創意剪貼:學生使用對摺技巧製作不同的幾何形狀,並將它們組合成一個有關斜屋頂的圖案或其他創意設計。
230
+ # 面積計算:學生計算他們設計中每種形狀的面積,以及整體設計的總面積。
231
+
232
+ # - 分享與討論 (10 分鐘)
233
+ # 學生展示他們的創作並分享他們的設計過程,包括面積如何影響他們的設計選擇。
234
+ # 討論學生在創作過程中遇到的挑戰和他們如何解決。
235
 
236
+ # - 總結 (5 分鐘)
237
+ # 回顧課程學到的主要概念。
238
+ # 鼓勵學生在家中嘗試更多的創意組合和面積計算。
239
 
240
+ # - 課後���動
241
+ # 建議學生使用家中的材料(如報紙、雜誌等)進行更多的創意剪貼,並嘗試計算不同形狀的面積,進一步鞏固今天學到的知識。
242
 
243
+ # 【教學策略總結】
244
+ # 通過這個課程計劃,學生不僅能夠學習數學知識,還能夠將這些知識應用於實際生活中,從而增強他們的創造力和問題解決能力。
245
+ # """
246
+
247
+ lesson_plan_5E_prompt = """
248
+ 你是一位專業教師,
249
+ 請根據以上要教學的項目細節(主題、年級、課程時間、課程目標)
250
+ 幫我安排一個 lesson plan
251
+
252
+ 規則如下,請嚴格遵守
253
+ 1. 請使用繁體中文溝通 zh-TW,並使用台灣人的用語
254
+ 2. 該換行就換行,盡量滿足 word .doc 的格式
255
+ 3. 【課程大綱】的工作項目請嚴格遵守【課程時間】的時間長度,總和時間不要超過或不足
256
+ 4. 時間安排盡量以五分鐘的倍數為一個單位
257
+ 5. 並且根據課程目標安排教學內容
258
+
259
+ Lesson Planner 5E is specialized in developing lesson plans for various subjects including Mathematics, Physics, Chemistry, and Biology, using the 5E instructional model. Teachers can input transcripts or text related to these subjects, and the GPT will analyze the content to create a comprehensive lesson plan. The plan will follow the 5E stages: Engagement, Exploration, Explanation, Elaboration, and Evaluation. Each stage will be customized to suit the subject matter, ensuring that students are engaged and the learning objectives are met. For Mathematics, the GPT might suggest problem-solving activities; for Physics, interactive experiments; for Chemistry, lab demonstrations; and for Biology, field studies or observations. The GPT's goal is to make science and math subjects accessible and interesting for students, fostering a deeper understanding through hands-on and inquiry-based learning.
260
+
261
+ Step1:理解什麼是 5E 教學模式,請閱讀以下文字:
262
+ 5E教學模式:
263
+ 由Trowbridge和Bybee (1990)提出以探究為基礎的5E教學模式,將教學過程劃分為五個緊密相連的階段,包括:
264
+ 參與(engagement)、探索(exploration)、解釋(explanation)、精緻化(elaboration)與評量(evaluation)等五個階段,
265
+ 各階段的內容如下:
266
+ 1.參與:以學生的學習為主體,設計活動引發學生的學習興趣,使學生願意主動參與教學活動,並能將學生的舊經驗與課程內容相連結,經由提問、定義問題與呈現矛盾的結果等方式,引出探討主題的方向。
267
+ 2.探索:學生參與活動,並給予足夠時間與機會進行探索任務,經由動手操作,建構共同的、具體的經驗。
268
+ 3.解釋:先請學生提出解釋,教師再以學生的想法為基礎,並運用口頭、影片或教學媒體等方式,對學生的解釋加以闡述確認,使學生能確實理解學科知識,再引導學生進入下一階段的教學流程。
269
+ 4.學習共同體 (精緻化):重視學生之間的互動,營造能促使學生討論以及互相合作的學習環境,分享想法並給予回饋,以建構個人的理解。此外,此階段亦重視學生是否能將其所形成的解釋,應用於新的情境或問題中,以延伸更加一般化的概念理解,進而獲取高層次的知識。
270
+ 5.評量:此階段的主要目的為鼓勵學生評估自己的理解力與能力,同時老師也藉由評量確認學生是否達成教學目標該有的程度。在學生進行探索與提出解釋後,給予回饋是相當重要的,因而教師在階段活動後應進行形成性評量
271
+
272
+ Step2:請根據逐字稿的內容描述,以 5E 架構來創建一份 Lesson Plan (課程計畫)。請依循並參考以下輸出範例來設計課程計畫),需使用 markdown 語法:
273
+
274
+ ## 輸出範例:
275
+
276
+ 課程主題:同分母分數的加減 (使用影片或逐字稿的標題)
277
+ 課程重點:(列出逐字稿內容的重要概念或教學重點)
278
+ 年級:四年級 (依據詢問的年齡或年級填入)
279
+ 課程長度:45-50 分鐘(根據課程數入時間訂定)
280
+
281
+ 【參與】(5 分鐘)
282
+
283
+ - 透過在黑板或投影機上顯示切成不同部分的披薩的圖片來開始課程。
284
+ - 讓學生與夥伴討論他們注意到分數的什麼以及如何將它們加在一起或減在一起。
285
+ - 幾分鐘後,請幾位學生與全班分享他們的觀察和想法。
286
+
287
+ 【探索】(10 分鐘)
288
+
289
+ - 發給每位學生分數操作工具���例如分數條、分數圓)。
290
+ - 為每個學生提供一份工作表,其中包含幾個涉及具有相同分母的分數的加法和減法問題。
291
+ - 指導學生使用操作工具解決工作表上的問題。
292
+ - 在房間裡走動,觀察學生的理解情況,並根據需要提供指導。
293
+
294
+ 【解釋】(10 分鐘)
295
+
296
+ - 讓全班同學重新聚集在一起,討論相同分母分數加法和減法的策略和方法。
297
+ - 介紹關鍵詞彙:分子、分母、分數、同分母。
298
+ - 顯示並解釋公式:如果兩個分數具有相同的分母,則將分子相加或相減,然後寫出公分母的和或差。
299
+ - 提供同分母分數加減法的例子,示範步驟和計算。
300
+
301
+ 【學習共同體】(15 分鐘)
302
+
303
+ - 將學生分成兩人一組或小組。
304
+ - 向每組分發一組分數加法和減法任務卡。
305
+ - 指導學生使用前面討論的方法一起解決任務卡上的問題。
306
+ - 鼓勵學生互相解釋他們的想法和推理。
307
+ - 在教室裡走動以了解學生的進度並在需要時提供支持或澄清。
308
+
309
+ 【評估】(10 分鐘)
310
+
311
+ - 要求每個小組選擇一張任務卡並向全班展示他們的解決方案。
312
+ - 作為一個班級,討論各小組使用的不同策略並評估他們解決方案的正確性。
313
+ - 提供回饋並澄清討論期間出現的任何誤解。
314
+ - 收集學生的工作表和任務卡,作為他們理解的形成性評估。
315
+
316
+ 【總結】
317
+
318
+ - 總結課程中討論的重點,強調分數加減法時分母相同的重要性。
319
+ - 請學生反思他們的學習,並寫一兩句話來說明他們認為今天的課程有挑戰性或有趣的地方。
320
+
321
+ 【學習評估】
322
+
323
+ - 形成性評估:探索和細化階段的觀察以及討論和演示將深入了解學生對相同分母分數加法和減法的理解。
324
+ - 總結性評估:收集並審查學生的工作表和任務卡,以評估他們解決涉及具有相同分母的分數的加法和減法問題的能力。
325
  """
326
+ lesson_plan_prompt = lesson_plan_5E_prompt
327
 
328
  return lesson_plan_prompt
329
 
local_config.json ADDED
@@ -0,0 +1,23 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "OUTPUT_PATH": "/Users/young/Downloads",
3
+ "TRANSCRIPTS": [],
4
+ "CURRENT_INDEX": 0,
5
+ "VIDEO_ID": "",
6
+ "PASSWORD": "6161",
7
+ "OPEN_AI_KEY": "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",
8
+ "GROQ_API_KEY": "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",
9
+ "JUTOR_CHAT_KEY": "b4c318b8d6f770e10163436e0e868b806f50f34ae57f378e78956fb76b41fd27",
10
+ "GOOGLE_APPLICATION_CREDENTIALS_JSON": {
11
+ "type": "service_account",
12
+ "project_id": "junyiacademy",
13
+ "private_key_id": "95d4580de7518ff96be219626c58b23f1754ec6a",
14
+ "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCmo3M8Ix46FLNS\nX8NGnEMiZBomXQOEfKCRUy0khm5IKgu5RUIWSF/0UuZeWXG93a1PMarBWokTTIpo\njtRNuEAh+H8lbacJ/G6EH5PxyMjPAOROXLvSJUg4c6lOFp/WoFlnirSPsD/YHL3l\nKKm6//h/tN7GDhaqOKrf9xJ1IlSt7odhCgcR7r3dF/+pbZ2UMDA0rmNmj2aJq7rl\n1qOD/8yC3ainledRRSt8oE7t4w+3eqI5GO7OdLiNwSxGGBNx9IiJln6TFC7frvET\nzr2ZUVLL7jFdWNyfXPU3/B+7JwyXgFGfHpiyQ2xyFAUR62HkpVxs0bKNbnLLmKoL\nIXCQ+lB9AgMBAAECggEAAJLA1C/E7E46SeEqdP3o/TbAwy+Ro4dnUaRNbaK9HmJv\nXymcYJc/JfxjBcRu0qV6wyaeaH1UVZJ67TtWX6JvuPQDWQ2dVX5eovW42aks/AlR\n1wSgeA5rr7pkrZ6jZWa2XGNfOLxpwaGpIqrcx7T/ija/Qc8qmShnSyx+awi9YdX4\n/ih1Ug1fqHPQ1j3Xx23J/S4Wb6eamJZ1BW+txZUUVD2KFnw7iJWR81ceTIuoSY4R\na3Vd8yB1Ri9iVaP6pe0WeA14VfU1d3tbO0GRF6XsEtdRynVXA17nO6okc3yV0bOQ\nnWqibtfT6AWmJFIyya3DrZDxKqSFNuVfHaJrO2LvwQKBgQDRGUzCYJpGw6NAQuKw\n3egfBoTjozZxjykZb0GFFRCIIE9AmX0PkCbz9bxjjMtb9pTH3aVIiv8mL3rfqzdK\nEPEfyC5TntWqahqBW+fOMrpnU54o4Ji1TlFuSBMRBxqIPcJnKFuttkfPZiHbxe3i\nkeqBbJ4HafH5ZNAV+pHwQKSXDwKBgQDMBAbxvSKgYXxeGMOs62rK6y9NeZlKpRUJ\nORdBv+3sLTwSjlpEPwn/KgMXHaHnpDXhtTDP0ad9nLOKnZ5k8dRNcpyUEXVC2WTD\nDHd37lChG0XRnfGuxIvfr5ZYajN/sIgV8WuNpFuWnFj+mGdi+OpzS+KG3J2rMx/k\n4jl1l8g/swKBgQCYJCVyxSFb3dt51XDmKgBMGs00aLwjcnwAErCEqagGtCOWKFgT\nq9p75dA2SupotojYyBAMMX6nBSMNfaHUFXGso1X55/clcOBqQTnwEX8J0ZChw5G5\nUgv0ByNAX3/Ro8ZAkt/qDFhBstlt9J07HtqXYzW0xUSYJt81LsVjH1XixwKBgDF6\nUIzUB8/JlLXlX59SQMYRn5k0gl6+BooEFIXXnEYI+matq2qdtzjw4Wr7vsZE5uRk\nYwAjonEuTcSyUTW+CHT39M1cJood2vgKz/aAD4Hi8V3S5kgyVpHbLaUnrHGtSHO5\n5xGk6KlwJY0pPPmd0I5BuyBl5L8eWP/TdYf1VZVNAoGAUEMSVxBd/onX5LXrzkR7\nOS8w6L+YL68yWCFs1pocfY+cZfkBpteLXzqOMKN2Xc3C7TGGySnEaQlG8bF1P00E\nW9HZvrSYbhsZwEKiIxY3suWRMPRlbeHU9hxt7rYPM+kwl8zy+Z/UtE28Zwhcr0+r\n7EkfwiOEvYStw0mJ9xwhiwU=\n-----END PRIVATE KEY-----\n",
15
+ "client_email": "[email protected]",
16
+ "client_id": "113360448655635325414",
17
+ "auth_uri": "https://accounts.google.com/o/oauth2/auth",
18
+ "token_uri": "https://oauth2.googleapis.com/token",
19
+ "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
20
+ "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/analytics-api%40junyiacademy.iam.gserviceaccount.com",
21
+ "universe_domain": "googleapis.com"
22
+ }
23
+ }
local_config_example.json ADDED
@@ -0,0 +1,12 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "OUTPUT_PATH": "/Users/young/Downloads",
3
+ "TRANSCRIPTS": [],
4
+ "CURRENT_INDEX": 0,
5
+ "VIDEO_ID": "",
6
+ "PASSWORD": "1234",
7
+ "OPEN_AI_KEY": "sk-",
8
+ "GROQ_API_KEY": "",
9
+ "JUTOR_CHAT_KEY": "",
10
+ "GOOGLE_APPLICATION_CREDENTIALS_JSON": {
11
+ }
12
+ }
storage_service.py ADDED
@@ -0,0 +1,44 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import json
2
+ from google.cloud import storage
3
+ from google.oauth2 import service_account
4
+ from googleapiclient.http import MediaIoBaseDownload
5
+
6
+
7
+ class GoogleCloudStorage:
8
+ def __init__(self, service_account_key_string):
9
+ credentials_dict = json.loads(service_account_key_string)
10
+ credentials = service_account.Credentials.from_service_account_info(credentials_dict)
11
+ self.client = storage.Client(credentials=credentials, project=credentials_dict['project_id'])
12
+
13
+ def check_file_exists(self, bucket_name, file_name):
14
+ blob = self.client.bucket(bucket_name).blob(file_name)
15
+ return blob.exists()
16
+
17
+ def upload_file(self, bucket_name, destination_blob_name, file_path):
18
+ blob = self.client.bucket(bucket_name).blob(destination_blob_name)
19
+ blob.upload_from_filename(file_path)
20
+ print(f"File {file_path} uploaded to {destination_blob_name} in GCS.")
21
+
22
+ def upload_file_as_string(self, bucket_name, destination_blob_name, content):
23
+ blob = self.client.bucket(bucket_name).blob(destination_blob_name)
24
+ blob.upload_from_string(content)
25
+ print(f"String content uploaded to {destination_blob_name} in GCS.")
26
+ return None
27
+
28
+ def download_as_string(self, bucket_name, source_blob_name):
29
+ blob = self.client.bucket(bucket_name).blob(source_blob_name)
30
+ return blob.download_as_text()
31
+
32
+ def make_blob_public(self, bucket_name, blob_name):
33
+ blob = self.client.bucket(bucket_name).blob(blob_name)
34
+ blob.make_public()
35
+ print(f"Blob {blob_name} is now publicly accessible at {blob.public_url}")
36
+
37
+ def get_public_url(self, bucket_name, blob_name):
38
+ blob = self.client.bucket(bucket_name).blob(blob_name)
39
+ return blob.public_url
40
+
41
+ def upload_image_and_get_public_url(self, bucket_name, file_name, file_path):
42
+ self.upload_file(bucket_name, file_name, file_path)
43
+ self.make_blob_public(bucket_name, file_name)
44
+ return self.get_public_url(bucket_name, file_name)