Spaces:
Running
Running
update
Browse files- app.py +41 -96
- educational_material.py +123 -42
- local_config.json +23 -0
- local_config_example.json +12 -0
- storage_service.py +44 -0
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 |
-
|
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':
|
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
|
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="
|
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 =
|
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 =
|
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 =
|
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 =
|
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 |
-
|
|
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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 =
|
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)
|