Spaces:
Sleeping
Sleeping
update
Browse files
app.py
CHANGED
|
@@ -7,15 +7,17 @@ import os
|
|
| 7 |
from openai import OpenAI
|
| 8 |
from groq import Groq
|
| 9 |
|
| 10 |
-
import json
|
| 11 |
-
|
| 12 |
from youtube_transcript_api import YouTubeTranscriptApi
|
| 13 |
from youtube_transcript_api._errors import NoTranscriptFound
|
| 14 |
-
|
| 15 |
|
| 16 |
from moviepy.editor import VideoFileClip
|
| 17 |
from pytube import YouTube
|
| 18 |
import os
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
from google.cloud import storage
|
| 21 |
from google.oauth2 import service_account
|
|
@@ -24,11 +26,13 @@ from googleapiclient.http import MediaFileUpload
|
|
| 24 |
from googleapiclient.http import MediaIoBaseDownload
|
| 25 |
from googleapiclient.http import MediaIoBaseUpload
|
| 26 |
|
| 27 |
-
import
|
| 28 |
-
|
|
|
|
|
|
|
|
|
|
| 29 |
|
| 30 |
|
| 31 |
-
from urllib.parse import urlparse, parse_qs
|
| 32 |
|
| 33 |
|
| 34 |
# 假设您的环境变量或Secret的名称是GOOGLE_APPLICATION_CREDENTIALS_JSON
|
|
@@ -381,6 +385,47 @@ def get_transcript(video_id):
|
|
| 381 |
continue # 當前語言的字幕沒有找到,繼續嘗試下一個語言
|
| 382 |
return None # 所有嘗試都失敗,返回None
|
| 383 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 384 |
def process_transcript_and_screenshots(video_id):
|
| 385 |
print("====process_transcript_and_screenshots====")
|
| 386 |
|
|
@@ -457,7 +502,13 @@ def process_transcript_and_screenshots_on_gcs(video_id):
|
|
| 457 |
is_transcript_exists = gcs_check_file_exists(gcs_client, bucket_name, transcript_blob_name)
|
| 458 |
if not is_transcript_exists:
|
| 459 |
# 从YouTube获取逐字稿并上传
|
| 460 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 461 |
if transcript:
|
| 462 |
print("成功獲取字幕")
|
| 463 |
else:
|
|
@@ -999,243 +1050,36 @@ def change_questions(password, df_string):
|
|
| 999 |
print("=====get_questions=====")
|
| 1000 |
return q1, q2, q3
|
| 1001 |
|
| 1002 |
-
# --- 學習單 ---
|
| 1003 |
-
def generate_worksheet(password, df_string, worksheet_topic, worksheet_grade, worksheet_level, worksheet_algorithm):
|
| 1004 |
-
verify_password(password)
|
| 1005 |
-
|
| 1006 |
-
# df_string delete embed_url, screenshot_path
|
| 1007 |
-
df_string_json = json.loads(df_string)
|
| 1008 |
-
for entry in df_string_json:
|
| 1009 |
-
entry.pop('embed_url', None)
|
| 1010 |
-
entry.pop('screenshot_path', None)
|
| 1011 |
-
df_string_text = json.dumps(df_string_json, ensure_ascii=False, indent=2)
|
| 1012 |
-
|
| 1013 |
-
worksheet_prompt = get_worksheet_prompt(worksheet_algorithm)
|
| 1014 |
-
|
| 1015 |
-
# 使用 OpenAI 生成基于上传数据的问题
|
| 1016 |
-
sys_content = "你是一個擅長資料分析跟影片教學備課的老師,請精讀資料文本,自行判斷資料的種類,使用 zh-TW"
|
| 1017 |
-
user_content = f"""
|
| 1018 |
-
這是逐字稿:{df_string_text}
|
| 1019 |
-
---
|
| 1020 |
-
這是學習單模型:{worksheet_algorithm}
|
| 1021 |
-
這是學習單科目:{worksheet_topic}
|
| 1022 |
-
這是學習單年級:{worksheet_grade}
|
| 1023 |
-
這是學習單難度:{worksheet_level}
|
| 1024 |
-
|
| 1025 |
-
請根據逐字稿進行以下工作:
|
| 1026 |
-
不要提到 【逐字稿】 這個詞,直接給出工作內容即可
|
| 1027 |
-
如果是中文素材,請嚴格使用 zh-TW
|
| 1028 |
-
{worksheet_prompt}
|
| 1029 |
-
"""
|
| 1030 |
-
messages = [
|
| 1031 |
-
{"role": "system", "content": sys_content},
|
| 1032 |
-
{"role": "user", "content": user_content}
|
| 1033 |
-
]
|
| 1034 |
-
|
| 1035 |
-
print("=====generate_worksheet messages=====")
|
| 1036 |
-
print(messages)
|
| 1037 |
-
print("=====generate_worksheet messages=====")
|
| 1038 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1039 |
request_payload = {
|
| 1040 |
-
"model":
|
| 1041 |
"messages": messages,
|
| 1042 |
-
"max_tokens": 4000
|
| 1043 |
}
|
|
|
|
|
|
|
| 1044 |
|
| 1045 |
-
|
| 1046 |
-
worksheet = response.choices[0].message.content.strip()
|
| 1047 |
-
print("=====worksheet=====")
|
| 1048 |
-
print(worksheet)
|
| 1049 |
-
print("=====worksheet=====")
|
| 1050 |
-
return worksheet, worksheet_prompt
|
| 1051 |
-
|
| 1052 |
-
def get_worksheet_prompt(algorithm="Bloom認知階層理論"):
|
| 1053 |
-
bloom_worksheet_prompt = """
|
| 1054 |
-
你是個專業的教師,熟悉布魯姆(Benjamin Bloom, 1964) 的認知理論。布魯姆認為人類的能力,大致可分為三個領域(domains),即認知領域(cognitive domain)、情意領域(affective domain)、技能領域 (psychomotor domain)。
|
| 1055 |
-
|
| 1056 |
-
【認知領域】涉及知能及其運作,著重心智、學習以及問題解決的工作。認知目標從簡單的認識或記憶能力到複雜的評鑑能力。大部分的教育目標都屬於這個領域。認知領域的目標分為六個層次,每個層次代表不同的心智功能。
|
| 1057 |
-
- 📖 知識:在認知目標中知識是最低層次的能力,包括名詞、事實、規則和原理原則等的認識和記憶。用來表示此種能力的行為動詞有:指出、寫出、界定、說明、舉例、命名、認明等。例:能在地圖上指出長江流經的省分。
|
| 1058 |
-
- 🤔 理解:理解是指能把握所學過知識或概念的意義,包含轉譯、解釋、推論等能力。代表此能力的行為動詞有:解釋、說明、區別、舉例、摘要、歸納等。例:能解釋光合作用。
|
| 1059 |
-
- 🛠️ 應用:應用是指將所學到的規則、方法、步驟、原理、原則和概念,應用到新情境的能力。用來表示此能力的行為動詞有:預測、證明、解決、修改、表現、應用等。例:學生能預測抽出容器內之氣體對容器的影響。
|
| 1060 |
-
- 🔍 分析:分析是指將所學到的概念或原則,分析為各個構成的部分,或找出各部分之間的相互關係,包括要素、關係及組織原理等的分析。用以表示此種能力的行為動詞有:選出、分析、判斷:區分、指出某些組成要素、指出某些的相互關係等。例:讀完某篇文章後,學生能區分事實和意見。
|
| 1061 |
-
- 🌐 綜合:綜合是指將所學到的片斷概念或知識、原理原則或事實,統整成新的整體。用來表示此種能力的行為動詞有:設計、組織、綜合、創造、歸納、聯合等。例:讀完一篇有關防治汙染的文章後,學生能綜合防治汙染的各種方法。
|
| 1062 |
-
- 🏅 評鑑:評鑑是認知目標中最高層次的能力,指依據某項標準做價值的判斷的能力。用來表示此能力的行為動詞有:評鑑、判斷、評論、比較、批判等。例:學生能評斷辯論中的謬論。
|
| 1063 |
-
|
| 1064 |
-
學習單包含以下的內容,將以布魯姆教育目標來建構提問的架構;請用 markdown 格式來呈現。
|
| 1065 |
-
- 📝 主題:請使用上傳檔案的檔名作為標題
|
| 1066 |
-
- 🔑 重點: 和影片有關重要知識摘要
|
| 1067 |
-
- 💭 概念:概念性問題 - 布魯姆的知識層級;數學知識的建構
|
| 1068 |
-
- 📊 計算:程序性問題 - 布魯姆的理解層級;和影片相同的例題,類似的練習題 → 計算與步驟操作
|
| 1069 |
-
- 🚀 延伸與應用 - 布魯姆的應用、分析、綜合、評鑑層級 → 延伸思考與應用
|
| 1070 |
-
|
| 1071 |
-
其中,「重點」的題目請用挖空的填空題;在「計算」的程序性問題請以單選題或填空題的形式來建立,需要 3 個題目;「延伸與應用」請使用問答題的形式來建立,一題即可。
|
| 1072 |
-
題目和題目之間要換行,並加上 point 符號,像是 "-" 或是 "1." 等等
|
| 1073 |
-
|
| 1074 |
-
這是範例格式:
|
| 1075 |
-
🌐 主題:【概念】認識公里
|
| 1076 |
-
|
| 1077 |
-
🎓【對象】
|
| 1078 |
-
科目: 數學
|
| 1079 |
-
年級: 三年級
|
| 1080 |
-
難度: 基礎
|
| 1081 |
-
|
| 1082 |
-
🏞️【情境描述】
|
| 1083 |
-
狐狸貓和家人出遊,過程中認識測量較長距離的單位「公里」。
|
| 1084 |
-
|
| 1085 |
-
🔑【影片重點】
|
| 1086 |
-
- 公里是用來測量長距離的單位,通常用於測量很遠的距離。
|
| 1087 |
-
- 1公里等於___公尺,也稱為千米。
|
| 1088 |
-
- 公里的英文簡寫是 ____。
|
| 1089 |
-
|
| 1090 |
-
🌟【概念】
|
| 1091 |
-
- 請問公里通常用於測量什麼類型的距離?
|
| 1092 |
-
- 如果一圈操場是200公尺,那跑5圈是多少公尺?多少公里?
|
| 1093 |
-
- 為什麼我們需要使用公里這個單位來測量距離?
|
| 1094 |
-
|
| 1095 |
-
🔢【計算】
|
| 1096 |
-
1. 一圈操場是200公尺,跑10圈是多少公里?(A) 1公里 (B) 2公里 (C) 3公里 (D) 4公里
|
| 1097 |
-
2. 如果你跑了5圈操場,運動手環上會顯示你跑了多少公里?
|
| 1098 |
-
3. 6000公尺等於多少公里?
|
| 1099 |
-
|
| 1100 |
-
💡【延伸與應用】
|
| 1101 |
-
- 假設你參加一場慈善路跑活動,全程是5公里。如果你已經跑了3公里,還剩下多少公里?你覺得這樣的活動對你的體能有什麼影響?
|
| 1102 |
-
"""
|
| 1103 |
-
|
| 1104 |
-
Polya_worksheet_prompt = """
|
| 1105 |
-
你是個專業的教師,熟悉 George Polya(1945) 的數學問題解決策略。
|
| 1106 |
-
Polya提出了一個四步驟的數學問題解決策略,在他影響深遠的經典著作 How to solve it《如何解題》中指出解難過程可分為四個階段:
|
| 1107 |
-
(1) 理解問題 (understanding the problem)
|
| 1108 |
-
(2) 設計解題策略 (devising a plan)
|
| 1109 |
-
(3) 按步解題 (carrying out the plan)
|
| 1110 |
-
(4) 回顧解答 (looking back) (edited)
|
| 1111 |
-
|
| 1112 |
-
請以此設計學習單並依據文本跟難度給予題目
|
| 1113 |
-
請一定要使用 zh-TW
|
| 1114 |
-
|
| 1115 |
-
這是範例格式:
|
| 1116 |
-
🌐 主題:【概念】認識公里
|
| 1117 |
-
|
| 1118 |
-
🎓【對象】
|
| 1119 |
-
科目: 數學
|
| 1120 |
-
年級: 三年級
|
| 1121 |
-
難度: 基礎
|
| 1122 |
-
|
| 1123 |
-
🏞️【情境描述】
|
| 1124 |
-
狐狸貓和家人出遊,過程中認識測量較長距離的單位「公里」。
|
| 1125 |
-
|
| 1126 |
-
❓【給出問題】
|
| 1127 |
-
- 如果日月潭環潭自行車道共30公里,狐狸貓騎行了13.7公里後休息,剩餘多少公里尚未騎行?
|
| 1128 |
-
|
| 1129 |
-
🤔【理解問題】
|
| 1130 |
-
首先,我們必須完全理解問題的所有細節。在此情境中,我們需要釐清以下幾點:
|
| 1131 |
-
- 日月潭環潭自行車道的總長度為30公里。
|
| 1132 |
-
- 狐狸貓已經騎行了13.7公里。
|
| 1133 |
-
- 我們的目標是計算狐狸貓還剩下多少公里未騎行。
|
| 1134 |
-
|
| 1135 |
-
🧭【設計解題策略】
|
| 1136 |
-
接下來,根據我們對問題的理解來設計解決問題的計劃。在此步驟中,我們決定採用哪種策略來解決問題。對於這個問題,最直接的計劃是使用算術減法:
|
| 1137 |
-
- 總公里數(30公里)減去已騎行的公里數(13.7公里)。
|
| 1138 |
-
|
| 1139 |
-
📝【按步解題】
|
| 1140 |
-
按照設計的計劃執行解題步驟。在這裡我們執行減法運算:
|
| 1141 |
-
30−13.7=16.3
|
| 1142 |
-
這意味著狐狸貓還有16.3公里未騎行。
|
| 1143 |
-
|
| 1144 |
-
✨【回顧解答】
|
| 1145 |
-
最後,解題完成後,我們回顧和反思整個解題過程。在這一步,我們驗證計算的結果是否符合邏輯,是否正確解決了原問題。
|
| 1146 |
-
同時,考慮是否有更有效或簡便的方法來解決類似的問題。在這個例子中,使用直接減法是最簡單直接的方法。
|
| 1147 |
-
但在其他情境下,可能需要考慮使用圖形、表格或代數表達式等其他策略來解決問題。
|
| 1148 |
-
"""
|
| 1149 |
-
|
| 1150 |
-
# CRA教學法
|
| 1151 |
-
cra_worksheet_prompt = """
|
| 1152 |
-
你是個專業的教師,熟悉CRA教學法,CRA教學法是一種教學策略,
|
| 1153 |
-
CRA正是一種用來促進學生學習和記憶數學的三步教學法,它闡明瞭用這種方式進行教學的具體步驟。
|
| 1154 |
-
CRA的三個步驟互相依賴,運用CRA能建立起一種概念結構,從而形成知識的意義關聯。
|
| 1155 |
-
CRA策略的第一個階段, 即實例(C)階段,是一個“做”的階段。在這一階段,教師用加工材料建模,這些材料包括彩色圓片、立方體、十進位積木、六形六色積木,以及分數積木,等等。在使用這些材料時,必須考慮到兒童的視覺、觸覺及動感經驗。
|
| 1156 |
-
階段二,即描述(R)階段,是一個“看”的階段。在這一-階段,具體的模型被改成了圖片展示,教師可用手繪圖片或貼紙來對概念進行解釋。
|
| 1157 |
-
最後一個階段,即抽象(A)階段,是一個抽象的“符號”階段, 在這一階段, 教師使用數字、字母等數學符號(如:2, 6, 3x, +,-等)來進行教學。
|
| 1158 |
-
運用CRA的前提是學生在學習“規則”前必須學會概念。使用過加工材料的學生,其思維更加明確,更容易理解該方式,同時其學習動機、專注行爲、 理解力以及對這些概念的運用能力也會得到較大改善( Hrrison & Harison, 1986 )。
|
| 1159 |
-
CRA策略可以有效地幫助學生理解以下幾個概念:早期數量關係、位值、計算、分數、小數、測量、幾何、貨幣、百分數、數基、應用題、概率以及統計等
|
| 1160 |
-
這是範例格式:
|
| 1161 |
-
🌐 主題:【概念】認識公里
|
| 1162 |
-
|
| 1163 |
-
🎓【對象】
|
| 1164 |
-
科目: 數學
|
| 1165 |
-
年級: 三年級
|
| 1166 |
-
難度: 基礎
|
| 1167 |
-
|
| 1168 |
-
【實例(C)階段】
|
| 1169 |
-
1. 用彩色圓片來解釋什麼是分數?
|
| 1170 |
-
2. 用立方體來解釋什麼是體積?
|
| 1171 |
-
3. 用十進位積木來解釋什麼是小數?
|
| 1172 |
-
|
| 1173 |
-
【描述(R)階段】
|
| 1174 |
-
1. 用手繪圖片來解釋什麼是分數?
|
| 1175 |
-
2. 用貼紙來解釋什麼是體積?
|
| 1176 |
-
3. 用手繪圖片來解釋什麼是小數?
|
| 1177 |
-
|
| 1178 |
-
【抽象(A)階段】
|
| 1179 |
-
1. 用數字來解釋什麼是分數?
|
| 1180 |
-
2. 用字母來解釋什麼是體積?
|
| 1181 |
-
3. 用數字來解釋什麼是小數?
|
| 1182 |
-
|
| 1183 |
-
"""
|
| 1184 |
-
|
| 1185 |
-
case = {
|
| 1186 |
-
"Bloom認知階層理論": bloom_worksheet_prompt,
|
| 1187 |
-
"Polya數學解題法": Polya_worksheet_prompt,
|
| 1188 |
-
"CRA教學法": cra_worksheet_prompt
|
| 1189 |
-
}
|
| 1190 |
-
|
| 1191 |
-
return case[algorithm]
|
| 1192 |
-
|
| 1193 |
-
def generate_exam_fine_tune_result(password, worksheet_prompt, df_string_output, exam_result, exam_result_fine_tune_prompt):
|
| 1194 |
verify_password(password)
|
| 1195 |
-
|
| 1196 |
-
|
| 1197 |
-
|
| 1198 |
-
|
| 1199 |
-
entry.pop('embed_url', None)
|
| 1200 |
-
entry.pop('screenshot_path', None)
|
| 1201 |
-
df_string_text = json.dumps(df_string_json, ensure_ascii=False, indent=2)
|
| 1202 |
-
|
| 1203 |
-
# 使用 OpenAI 生成基于上传数据的问题
|
| 1204 |
-
sys_content = "你是一個擅長資料分析跟影片教學備課的老師,請精讀資料文本,自行判斷資料的種類,使用 zh-TW"
|
| 1205 |
-
user_content = f"""
|
| 1206 |
-
這是逐字稿:{df_string_text}
|
| 1207 |
-
---
|
| 1208 |
-
這是預設的 prompt
|
| 1209 |
-
{worksheet_prompt}
|
| 1210 |
-
---
|
| 1211 |
-
產生了以下的結果:
|
| 1212 |
-
{exam_result}
|
| 1213 |
-
---
|
| 1214 |
-
但我不是很滿意,請根據以下的調整,產生新的結果
|
| 1215 |
-
{exam_result_fine_tune_prompt}
|
| 1216 |
-
"""
|
| 1217 |
-
messages = [
|
| 1218 |
-
{"role": "system", "content": sys_content},
|
| 1219 |
-
{"role": "user", "content": user_content}
|
| 1220 |
-
]
|
| 1221 |
-
|
| 1222 |
-
print("=====generate_exam_fine_tune_result messages=====")
|
| 1223 |
-
print(messages)
|
| 1224 |
-
print("=====generate_exam_fine_tune_result messages=====")
|
| 1225 |
-
|
| 1226 |
request_payload = {
|
| 1227 |
-
"model":
|
| 1228 |
"messages": messages,
|
| 1229 |
-
"max_tokens": 4000
|
| 1230 |
}
|
| 1231 |
-
|
| 1232 |
-
|
| 1233 |
-
exam_fine_tune_result = response.choices[0].message.content.strip()
|
| 1234 |
-
print("=====exam_fine_tune_result=====")
|
| 1235 |
-
print(exam_fine_tune_result)
|
| 1236 |
-
print("=====exam_fine_tune_result=====")
|
| 1237 |
-
return exam_fine_tune_result
|
| 1238 |
-
|
| 1239 |
|
| 1240 |
# ---- Chatbot ----
|
| 1241 |
def respond(password, user_message, data, chat_history, socratic_mode=False):
|
|
@@ -1689,6 +1533,41 @@ def next_slide():
|
|
| 1689 |
return update_slide(1)
|
| 1690 |
|
| 1691 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1692 |
|
| 1693 |
HEAD = """
|
| 1694 |
<meta charset="UTF-8">
|
|
@@ -1720,11 +1599,11 @@ HEAD = """
|
|
| 1720 |
|
| 1721 |
with gr.Blocks() as demo:
|
| 1722 |
with gr.Row():
|
| 1723 |
-
password = gr.Textbox(label="Password", type="password", elem_id="password_input")
|
| 1724 |
file_upload = gr.File(label="Upload your CSV or Word file", visible=False)
|
| 1725 |
-
youtube_link = gr.Textbox(label="Enter YouTube Link", elem_id="youtube_link_input")
|
| 1726 |
video_id = gr.Textbox(label="video_id", visible=False)
|
| 1727 |
-
youtube_link_btn = gr.Button("Submit_YouTube_Link")
|
| 1728 |
web_link = gr.Textbox(label="Enter Web Page Link", visible=False)
|
| 1729 |
with gr.Tab("學生版"):
|
| 1730 |
with gr.Row():
|
|
@@ -1781,20 +1660,27 @@ with gr.Blocks() as demo:
|
|
| 1781 |
|
| 1782 |
with gr.Tab("教師版"):
|
| 1783 |
with gr.Row():
|
| 1784 |
-
gr.
|
|
|
|
|
|
|
| 1785 |
with gr.Row():
|
| 1786 |
with gr.Column(scale=1):
|
| 1787 |
# with gr.Tab("認知階層評量題目"):
|
| 1788 |
# cognitive_level_content = gr.Textbox(label="輸入學習目標與內容")
|
| 1789 |
# cognitive_level_content_btn = gr.Button("生成評量題目")
|
| 1790 |
with gr.Tab("學習單"):
|
| 1791 |
-
|
| 1792 |
-
|
| 1793 |
-
|
| 1794 |
-
worksheet_algorithm = gr.Dropdown(label="選擇學習單理論", choices=["Bloom認知階層理論", "Polya數學解題法", "CRA教學法"], value="Bloom認知階層理論")
|
| 1795 |
-
worksheet_content_btn = gr.Button("生成學習單")
|
| 1796 |
with gr.Accordion("prompt", open=False):
|
| 1797 |
worksheet_prompt = gr.Textbox(label="worksheet_prompt", show_copy_button=True, lines=40)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1798 |
with gr.Tab("素養導向閱讀題組"):
|
| 1799 |
literacy_oriented_reading_content = gr.Textbox(label="輸入閱讀材料")
|
| 1800 |
literacy_oriented_reading_content_btn = gr.Button("生成閱讀理解題")
|
|
@@ -1807,8 +1693,9 @@ with gr.Blocks() as demo:
|
|
| 1807 |
# with gr.Tab("後設認知"):
|
| 1808 |
# metacognition_content = gr.Textbox(label="輸入後設認知相關問題")
|
| 1809 |
# metacognition_content_btn = gr.Button("生成後設認知問題")
|
| 1810 |
-
with gr.Column(scale=
|
| 1811 |
# 生成對應不同模式的結果
|
|
|
|
| 1812 |
exam_result = gr.Textbox(label="初次生成結果", show_copy_button=True)
|
| 1813 |
exam_result_fine_tune_prompt = gr.Textbox(label="根據結果,輸入你想更改的想法")
|
| 1814 |
exam_result_fine_tune_btn = gr.Button("微調結果")
|
|
@@ -1919,14 +1806,27 @@ with gr.Blocks() as demo:
|
|
| 1919 |
|
| 1920 |
# 教師版 學習單
|
| 1921 |
worksheet_content_btn.click(
|
| 1922 |
-
|
| 1923 |
-
inputs=[password, df_string_output,
|
| 1924 |
-
outputs=[exam_result, worksheet_prompt]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1925 |
)
|
|
|
|
|
|
|
| 1926 |
exam_result_fine_tune_btn.click(
|
| 1927 |
generate_exam_fine_tune_result,
|
| 1928 |
-
inputs=[password,
|
| 1929 |
outputs=[exam_result_fine_result]
|
| 1930 |
)
|
| 1931 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1932 |
demo.launch(allowed_paths=["videos"])
|
|
|
|
| 7 |
from openai import OpenAI
|
| 8 |
from groq import Groq
|
| 9 |
|
|
|
|
|
|
|
| 10 |
from youtube_transcript_api import YouTubeTranscriptApi
|
| 11 |
from youtube_transcript_api._errors import NoTranscriptFound
|
| 12 |
+
import yt_dlp
|
| 13 |
|
| 14 |
from moviepy.editor import VideoFileClip
|
| 15 |
from pytube import YouTube
|
| 16 |
import os
|
| 17 |
+
import io
|
| 18 |
+
import time
|
| 19 |
+
import json
|
| 20 |
+
from urllib.parse import urlparse, parse_qs
|
| 21 |
|
| 22 |
from google.cloud import storage
|
| 23 |
from google.oauth2 import service_account
|
|
|
|
| 26 |
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
|
|
|
|
| 385 |
continue # 當前語言的字幕沒有找到,繼續嘗試下一個語言
|
| 386 |
return None # 所有嘗試都失敗,返回None
|
| 387 |
|
| 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': [{
|
| 394 |
+
'key': 'FFmpegExtractAudio',
|
| 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(
|
| 409 |
+
model="whisper-1",
|
| 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
|
| 417 |
+
segments = srt_content.segments
|
| 418 |
+
transcription = [
|
| 419 |
+
{
|
| 420 |
+
"text": item["text"],
|
| 421 |
+
"start": int(item["start"]),
|
| 422 |
+
"duration": int(item["end"] - item["start"])
|
| 423 |
+
}
|
| 424 |
+
for item in segments
|
| 425 |
+
]
|
| 426 |
+
return transcription
|
| 427 |
+
|
| 428 |
+
|
| 429 |
def process_transcript_and_screenshots(video_id):
|
| 430 |
print("====process_transcript_and_screenshots====")
|
| 431 |
|
|
|
|
| 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:
|
| 506 |
+
transcript = get_transcript(video_id)
|
| 507 |
+
except:
|
| 508 |
+
# call open ai whisper
|
| 509 |
+
print("===call open ai whisper===")
|
| 510 |
+
transcript = generate_transcription(video_id)
|
| 511 |
+
|
| 512 |
if transcript:
|
| 513 |
print("成功獲取字幕")
|
| 514 |
else:
|
|
|
|
| 1050 |
print("=====get_questions=====")
|
| 1051 |
return q1, q2, q3
|
| 1052 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1053 |
|
| 1054 |
+
# AI 生成教學素材
|
| 1055 |
+
def on_generate_ai_content(password, df_string, topic, grade, level, specific_feature, content_type):
|
| 1056 |
+
verify_password(password)
|
| 1057 |
+
material = EducationalMaterial(df_string, topic, grade, level, specific_feature, content_type)
|
| 1058 |
+
prompt = material.generate_content_prompt()
|
| 1059 |
+
user_content = material.build_user_content()
|
| 1060 |
+
messages = material.build_messages(user_content)
|
| 1061 |
+
ai_model_name = "gpt-4-1106-preview"
|
| 1062 |
request_payload = {
|
| 1063 |
+
"model": ai_model_name,
|
| 1064 |
"messages": messages,
|
| 1065 |
+
"max_tokens": 4000 # 举例,实际上您可能需要更详细的配置
|
| 1066 |
}
|
| 1067 |
+
ai_content = material.send_ai_request(OPEN_AI_CLIENT, request_payload)
|
| 1068 |
+
return ai_content, prompt, prompt
|
| 1069 |
|
| 1070 |
+
def generate_exam_fine_tune_result(password, exam_result_prompt , df_string_output, exam_result, exam_result_fine_tune_prompt):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1071 |
verify_password(password)
|
| 1072 |
+
material = EducationalMaterial(df_string_output, "", "", "", "", "")
|
| 1073 |
+
user_content = material.build_fine_tune_user_content(exam_result_prompt, exam_result, exam_result_fine_tune_prompt)
|
| 1074 |
+
messages = material.build_messages(user_content)
|
| 1075 |
+
ai_model_name = "gpt-4-1106-preview"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1076 |
request_payload = {
|
| 1077 |
+
"model": ai_model_name,
|
| 1078 |
"messages": messages,
|
| 1079 |
+
"max_tokens": 4000 # 举例,实际上您可能需要更详细的配置
|
| 1080 |
}
|
| 1081 |
+
ai_content = material.send_ai_request(OPEN_AI_CLIENT, request_payload)
|
| 1082 |
+
return ai_content
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1083 |
|
| 1084 |
# ---- Chatbot ----
|
| 1085 |
def respond(password, user_message, data, chat_history, socratic_mode=False):
|
|
|
|
| 1533 |
return update_slide(1)
|
| 1534 |
|
| 1535 |
|
| 1536 |
+
IS_PASSWORD_SHOW = True
|
| 1537 |
+
IS_YOUTUBE_LINK_SHOW = True
|
| 1538 |
+
IS_YOUTUBE_LINK_BTN_SHOW = True
|
| 1539 |
+
|
| 1540 |
+
def init_params(text, request: gr.Request):
|
| 1541 |
+
if request:
|
| 1542 |
+
print("Request headers dictionary:", request.headers)
|
| 1543 |
+
print("IP address:", request.client.host)
|
| 1544 |
+
print("Query parameters:", dict(request.query_params))
|
| 1545 |
+
# url = request.url
|
| 1546 |
+
print("Request URL:", request.url)
|
| 1547 |
+
|
| 1548 |
+
# if youtube_link in query_params
|
| 1549 |
+
if "youtube_id" in request.query_params:
|
| 1550 |
+
youtube_id = request.query_params["youtube_id"]
|
| 1551 |
+
youtube_link = f"https://www.youtube.com/watch?v={youtube_id}"
|
| 1552 |
+
print(f"youtube_link: {youtube_link}")
|
| 1553 |
+
else:
|
| 1554 |
+
youtube_link = ""
|
| 1555 |
+
print("youtube_link not in query_params")
|
| 1556 |
+
|
| 1557 |
+
origin = request.headers.get("origin", "")
|
| 1558 |
+
if "junyiacademy" in origin:
|
| 1559 |
+
password_text = "6161"
|
| 1560 |
+
global IS_PASSWORD_SHOW
|
| 1561 |
+
global IS_YOUTUBE_LINK_SHOW
|
| 1562 |
+
global IS_YOUTUBE_LINK_BTN_SHOW
|
| 1563 |
+
IS_PASSWORD_SHOW = False
|
| 1564 |
+
IS_YOUTUBE_LINK_SHOW = False
|
| 1565 |
+
IS_YOUTUBE_LINK_BTN_SHOW = False
|
| 1566 |
+
|
| 1567 |
+
else:
|
| 1568 |
+
password_text = ""
|
| 1569 |
+
|
| 1570 |
+
return password_text, youtube_link
|
| 1571 |
|
| 1572 |
HEAD = """
|
| 1573 |
<meta charset="UTF-8">
|
|
|
|
| 1599 |
|
| 1600 |
with gr.Blocks() as demo:
|
| 1601 |
with gr.Row():
|
| 1602 |
+
password = gr.Textbox(label="Password", type="password", elem_id="password_input", visible=IS_PASSWORD_SHOW)
|
| 1603 |
file_upload = gr.File(label="Upload your CSV or Word file", visible=False)
|
| 1604 |
+
youtube_link = gr.Textbox(label="Enter YouTube Link", elem_id="youtube_link_input", visible=IS_YOUTUBE_LINK_SHOW)
|
| 1605 |
video_id = gr.Textbox(label="video_id", visible=False)
|
| 1606 |
+
youtube_link_btn = gr.Button("Submit_YouTube_Link", elem_id="youtube_link_btn", visible=IS_YOUTUBE_LINK_BTN_SHOW)
|
| 1607 |
web_link = gr.Textbox(label="Enter Web Page Link", visible=False)
|
| 1608 |
with gr.Tab("學生版"):
|
| 1609 |
with gr.Row():
|
|
|
|
| 1660 |
|
| 1661 |
with gr.Tab("教師版"):
|
| 1662 |
with gr.Row():
|
| 1663 |
+
content_topic = gr.Dropdown(label="選擇主題", choices=["數學", "自然", "國文", "英文", "社會"], value="數學")
|
| 1664 |
+
content_grade = gr.Dropdown(label="選擇年級", choices=["一年級", "二年級", "三年級", "四年級", "五年級", "六年級", "七年級", "八年級", "九年級", "十年級", "十一年級", "十二年級"], value="三年級")
|
| 1665 |
+
content_level = gr.Dropdown(label="差異化教學", choices=["基礎", "中級", "進階"], value="基礎")
|
| 1666 |
with gr.Row():
|
| 1667 |
with gr.Column(scale=1):
|
| 1668 |
# with gr.Tab("認知階層評量題目"):
|
| 1669 |
# cognitive_level_content = gr.Textbox(label="輸入學習目標與內容")
|
| 1670 |
# cognitive_level_content_btn = gr.Button("生成評量題目")
|
| 1671 |
with gr.Tab("學習單"):
|
| 1672 |
+
worksheet_content_type_name = gr.Textbox(value="worksheet", visible=False)
|
| 1673 |
+
worksheet_algorithm = gr.Dropdown(label="選擇教學策略或理論", choices=["Bloom認知階層理論", "Polya數學解題法", "CRA教學法"], value="Bloom認知階層理論")
|
| 1674 |
+
worksheet_content_btn = gr.Button("生成學習單 📄")
|
|
|
|
|
|
|
| 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 |
+
|
| 1684 |
with gr.Tab("素養導向閱讀題組"):
|
| 1685 |
literacy_oriented_reading_content = gr.Textbox(label="輸入閱讀材料")
|
| 1686 |
literacy_oriented_reading_content_btn = gr.Button("生成閱讀理解題")
|
|
|
|
| 1693 |
# with gr.Tab("後設認知"):
|
| 1694 |
# metacognition_content = gr.Textbox(label="輸入後設認知相關問題")
|
| 1695 |
# metacognition_content_btn = gr.Button("生成後設認知問題")
|
| 1696 |
+
with gr.Column(scale=2):
|
| 1697 |
# 生成對應不同模式的結果
|
| 1698 |
+
exam_result_prompt = gr.Textbox(visible=False)
|
| 1699 |
exam_result = gr.Textbox(label="初次生成結果", show_copy_button=True)
|
| 1700 |
exam_result_fine_tune_prompt = gr.Textbox(label="根據結果,輸入你想更改的想法")
|
| 1701 |
exam_result_fine_tune_btn = gr.Button("微調結果")
|
|
|
|
| 1806 |
|
| 1807 |
# 教師版 學習單
|
| 1808 |
worksheet_content_btn.click(
|
| 1809 |
+
on_generate_ai_content,
|
| 1810 |
+
inputs=[password, df_string_output, content_topic, content_grade, content_level, worksheet_algorithm, worksheet_content_type_name],
|
| 1811 |
+
outputs=[exam_result, worksheet_prompt, exam_result_prompt]
|
| 1812 |
+
)
|
| 1813 |
+
lesson_plan_btn.click(
|
| 1814 |
+
on_generate_ai_content,
|
| 1815 |
+
inputs=[password, df_string_output, content_topic, content_grade, content_level, lesson_plan_time, lesson_plan_content_type_name],
|
| 1816 |
+
outputs=[exam_result, lesson_plan_prompt, exam_result_prompt]
|
| 1817 |
)
|
| 1818 |
+
|
| 1819 |
+
# 生成結果微調
|
| 1820 |
exam_result_fine_tune_btn.click(
|
| 1821 |
generate_exam_fine_tune_result,
|
| 1822 |
+
inputs=[password, exam_result_prompt, df_string_output, exam_result, exam_result_fine_tune_prompt],
|
| 1823 |
outputs=[exam_result_fine_result]
|
| 1824 |
)
|
| 1825 |
|
| 1826 |
+
demo.load(
|
| 1827 |
+
init_params,
|
| 1828 |
+
inputs =[youtube_link],
|
| 1829 |
+
outputs = [password , youtube_link]
|
| 1830 |
+
)
|
| 1831 |
+
|
| 1832 |
demo.launch(allowed_paths=["videos"])
|