youngtsai commited on
Commit
f4feb7d
·
1 Parent(s): b326bfd
Files changed (1) hide show
  1. app.py +379 -453
app.py CHANGED
@@ -427,7 +427,6 @@ def get_video_duration(video_id):
427
  def process_transcript_and_screenshots_on_gcs(video_id):
428
  print("====process_transcript_and_screenshots_on_gcs====")
429
  # GCS
430
- gcs_client = GCS_CLIENT
431
  bucket_name = 'video_ai_assistant'
432
  # 逐字稿文件名
433
  transcript_file_name = f'{video_id}_transcript.json'
@@ -552,9 +551,6 @@ def process_youtube_link(password, link):
552
  }
553
  formatted_simple_transcript.append(simple_line)
554
 
555
- global TRANSCRIPTS
556
- TRANSCRIPTS = formatted_transcript
557
-
558
  # 基于逐字稿生成其他所需的输出
559
  source = "gcs"
560
  questions_answers = get_questions_answers(video_id, formatted_simple_transcript, source)
@@ -568,9 +564,6 @@ def process_youtube_link(password, link):
568
  key_moments_html = get_key_moments_html(key_moments)
569
  html_content = format_transcript_to_html(formatted_transcript)
570
  simple_html_content = format_simple_transcript_to_html(formatted_simple_transcript)
571
- first_image = formatted_transcript[0]['screenshot_path']
572
- # first_image = "https://www.nameslook.com/names/dfsadf-nameslook.png"
573
- first_text = formatted_transcript[0]['text']
574
  mind_map_json = get_mind_map(video_id, formatted_simple_transcript, source)
575
  mind_map = mind_map_json["mind_map"]
576
  mind_map_html = get_mind_map_html(mind_map)
@@ -593,8 +586,6 @@ def process_youtube_link(password, link):
593
  mind_map_html, \
594
  html_content, \
595
  simple_html_content, \
596
- first_image, \
597
- first_text, \
598
  reading_passage_text, \
599
  reading_passage, \
600
  subject, \
@@ -694,6 +685,85 @@ def screenshot_youtube_video(youtube_id, snapshot_sec):
694
 
695
 
696
  # ---- LLM Generator ----
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
697
  def get_reading_passage(video_id, df_string, source):
698
  if source == "gcs":
699
  print("===get_reading_passage on gcs===")
@@ -738,62 +808,30 @@ def get_reading_passage(video_id, df_string, source):
738
  return reading_passage_json
739
 
740
  def generate_reading_passage(df_string):
741
- # 使用 OpenAI 生成基于上传数据的问题
742
- sys_content = "你是一個擅長資料分析跟影片教學的老師,user 為學生,請精讀資料文本,自行判斷資料的種類,使用 zh-TW"
743
- user_content = f"""
744
- 請根據 {df_string}
745
- 文本自行判斷資料的種類
746
- 幫我組合成 Reading Passage
747
- 並潤稿讓文句通順
748
- 請一定要使用繁體中文 zh-TW,並用台灣人的口語
749
- 產生的結果不要前後文解釋,也不要敘述這篇文章怎麼產生的
750
- 只需要專注提供 Reading Passage,字數在 500 字以內
751
- 敘述中,請把數學或是專業術語,用 Latex 包覆($...$),並且不要去改原本的文章
752
- 加減乘除、根號、次方等等的運算式口語也換成 LATEX 數學符號
753
- """
754
-
755
- try:
756
- # 使用 OPEN AI 生成 Reading Passage
757
- messages = [
758
- {"role": "system", "content": sys_content},
759
- {"role": "user", "content": user_content}
760
- ]
761
-
762
- request_payload = {
763
- "model": "gpt-4-turbo",
764
- "messages": messages,
765
- "max_tokens": 4000,
766
- }
767
-
768
- response = OPEN_AI_CLIENT.chat.completions.create(**request_payload)
769
- reading_passage = response.choices[0].message.content.strip()
770
- except:
771
- # 使用 REDROCK 生成 Reading Passage
772
- messages = [
773
- {"role": "user", "content": user_content}
774
- ]
775
- model_id = "anthropic.claude-3-sonnet-20240229-v1:0"
776
- # model_id = "anthropic.claude-3-haiku-20240307-v1:0"
777
- kwargs = {
778
- "modelId": model_id,
779
- "contentType": "application/json",
780
- "accept": "application/json",
781
- "body": json.dumps({
782
- "anthropic_version": "bedrock-2023-05-31",
783
- "max_tokens": 4000,
784
- "system": sys_content,
785
- "messages": messages
786
- })
787
- }
788
- response = BEDROCK_CLIENT.invoke_model(**kwargs)
789
- response_body = json.loads(response.get('body').read())
790
- reading_passage = response_body.get('content')[0].get('text')
791
-
792
- print("=====reading_passage=====")
793
- print(reading_passage)
794
- print("=====reading_passage=====")
795
-
796
- return reading_passage
797
 
798
  def text_to_speech(video_id, text):
799
  tts = gTTS(text, lang='en')
@@ -846,55 +884,23 @@ def get_mind_map(video_id, df_string, source):
846
  return mind_map_json
847
 
848
  def generate_mind_map(df_string):
849
- # 使用 OpenAI 生成基于上传数据的问题
850
- sys_content = "你是一個擅長資料分析跟影片教學的老師,user 為學生,請精讀資料文本,自行判斷資料的種類,使用 zh-TW"
851
- user_content = f"""
852
- 請根據 {df_string} 文本建立 markdown 心智圖
853
- 注意:不需要前後文敘述,直接給出 markdown 文本即可
854
- 這對我很重要
855
- """
856
-
857
- try:
858
- # 使用 OPEN AI 生成
859
- messages = [
860
- {"role": "system", "content": sys_content},
861
- {"role": "user", "content": user_content}
862
- ]
863
-
864
- request_payload = {
865
- "model": "gpt-4-turbo",
866
- "messages": messages,
867
- "max_tokens": 4000,
868
- }
869
-
870
- response = OPEN_AI_CLIENT.chat.completions.create(**request_payload)
871
- mind_map = response.choices[0].message.content.strip()
872
- except:
873
- # 使用 REDROCK 生成
874
- messages = [
875
- {"role": "user", "content": user_content}
876
- ]
877
- model_id = "anthropic.claude-3-sonnet-20240229-v1:0"
878
- # model_id = "anthropic.claude-3-haiku-20240307-v1:0"
879
- kwargs = {
880
- "modelId": model_id,
881
- "contentType": "application/json",
882
- "accept": "application/json",
883
- "body": json.dumps({
884
- "anthropic_version": "bedrock-2023-05-31",
885
- "max_tokens": 4000,
886
- "system": sys_content,
887
- "messages": messages
888
- })
889
- }
890
- response = BEDROCK_CLIENT.invoke_model(**kwargs)
891
- response_body = json.loads(response.get('body').read())
892
- mind_map = response_body.get('content')[0].get('text')
893
- print("=====mind_map=====")
894
- print(mind_map)
895
- print("=====mind_map=====")
896
 
897
- return mind_map
 
 
898
 
899
  def get_mind_map_html(mind_map):
900
  mind_map_markdown = mind_map.replace("```markdown", "").replace("```", "")
@@ -963,6 +969,7 @@ def get_video_id_summary(video_id, df_string, source):
963
  return summary_json
964
 
965
  def generate_summarise(df_string, metadata=None):
 
966
  # 使用 OpenAI 生成基于上传数据的问题
967
  if metadata:
968
  title = metadata.get("title", "")
@@ -973,89 +980,86 @@ def generate_summarise(df_string, metadata=None):
973
  subject = ""
974
  grade = ""
975
 
976
- sys_content = "你是一個擅長資料分析跟影片教學的老師,user 為學生,請精讀資料文本,自行判斷資料的種類,使用 zh-TW"
977
- user_content = f"""
978
- 課程名稱:{title}
979
- 科目:{subject}
980
- 年級:{grade}
981
-
982
- 請根據內文: {df_string}
983
-
984
- 格式為 Markdown
985
- 如果有課程名稱,請圍繞「課程名稱」為學習重點,進行重點整理,不要整理跟情境故事相關的問題
986
- 整體摘要在一百字以內
987
- 重點概念列出 bullet points,至少三個,最多五個
988
- 以及可能的結論與結尾延伸小問題提供學生作反思
989
- 敘述中,請把數學或是專業術語,用 Latex 包覆($...$)
990
- 加減乘除、根號、次方等等的運算式口語也換成 LATEX 數學符號
991
-
992
- 整體格式為:
993
- ## 🌟 主題:{{title}} (如果沒有 title 就省略)
994
- ## 📚 整體摘要
995
- - (一個 bullet point....)
996
-
997
- ## 🔖 重點概念
998
- - xxx
999
- - xxx
1000
- - xxx
1001
-
1002
- ## 💡 為什麼我們要學這個?
1003
- - (一個 bullet point....)
1004
-
1005
- ## ❓ 延伸小問題
1006
- - (一個 bullet point....請圍繞「課程名稱」為學習重點,進行重點整理,不要整理跟情境故事相關的問題)
1007
- """
1008
-
1009
- # 🗂️ 1. 內容類型:?
1010
- # 📚 2. 整體摘要
1011
- # 🔖 3. 條列式重點
1012
- # 🔑 4. 關鍵時刻(段落摘要)
1013
- # 💡 5. 結論反思(為什麼我們要學這個?)
1014
- # ❓ 6. 延伸小問題
1015
 
1016
- try:
1017
- #OPEN AI
1018
- messages = [
1019
- {"role": "system", "content": sys_content},
1020
- {"role": "user", "content": user_content}
1021
- ]
1022
 
1023
- request_payload = {
1024
- "model": "gpt-4-turbo",
1025
- "messages": messages,
1026
- "max_tokens": 4000,
1027
- }
1028
-
1029
- response = OPEN_AI_CLIENT.chat.completions.create(**request_payload)
1030
- df_summarise = response.choices[0].message.content.strip()
1031
- except:
1032
- #REDROCK
1033
- messages = [
1034
- {"role": "user", "content": user_content}
1035
- ]
1036
- model_id = "anthropic.claude-3-sonnet-20240229-v1:0"
1037
- # model_id = "anthropic.claude-3-haiku-20240307-v1:0"
1038
- kwargs = {
1039
- "modelId": model_id,
1040
- "contentType": "application/json",
1041
- "accept": "application/json",
1042
- "body": json.dumps({
1043
- "anthropic_version": "bedrock-2023-05-31",
1044
- "max_tokens": 4000,
1045
- "system": sys_content,
1046
- "messages": messages
1047
- })
1048
- }
1049
- response = BEDROCK_CLIENT.invoke_model(**kwargs)
1050
- response_body = json.loads(response.get('body').read())
1051
- df_summarise = response_body.get('content')[0].get('text')
1052
-
1053
-
1054
- print("=====df_summarise=====")
1055
- print(df_summarise)
1056
- print("=====df_summarise=====")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1057
 
1058
- return df_summarise
1059
 
1060
  def get_questions(video_id, df_string, source="gcs"):
1061
  if source == "gcs":
@@ -1110,6 +1114,7 @@ def get_questions(video_id, df_string, source="gcs"):
1110
  return q1, q2, q3
1111
 
1112
  def generate_questions(df_string):
 
1113
  # 使用 OpenAI 生成基于上传数据的问题
1114
  if isinstance(df_string, str):
1115
  df_string_json = json.loads(df_string)
@@ -1121,9 +1126,19 @@ def generate_questions(df_string):
1121
  content_text += entry["text"] + ","
1122
 
1123
  sys_content = "你是一個擅長資料分析跟影片教學的老師,user 為學生,請精讀資料文本,自行判斷資料的種類,並用既有資料為本質猜測用戶可能會問的問題,使用 zh-TW"
1124
- user_content = f"請根據 {content_text} 生成三個問題,並用 JSON 格式返回 questions:[q1的敘述text, q2的敘述text, q3的敘述text]"
 
 
 
 
 
 
 
 
 
1125
 
1126
  try:
 
1127
  messages = [
1128
  {"role": "system", "content": sys_content},
1129
  {"role": "user", "content": user_content}
@@ -1136,7 +1151,7 @@ def generate_questions(df_string):
1136
 
1137
 
1138
  request_payload = {
1139
- "model": "gpt-4-turbo",
1140
  "messages": messages,
1141
  "max_tokens": 4000,
1142
  "response_format": response_format
@@ -1192,69 +1207,48 @@ def get_questions_answers(video_id, df_string, source="gcs"):
1192
  print("questions_answers已存在于GCS中")
1193
  questions_answers_text = GCS_SERVICE.download_as_string(bucket_name, blob_name)
1194
  questions_answers = json.loads(questions_answers_text)
1195
- except:
 
1196
  questions = get_questions(video_id, df_string, source)
1197
  questions_answers = [{"question": q, "answer": ""} for q in questions]
1198
 
1199
  return questions_answers
1200
 
1201
  def generate_questions_answers(df_string):
1202
- content_text = str(df_string)
1203
- sys_content = "你是一個擅長資料分析跟影片教學的老師,user 為學生,請精讀資料文本,自行判斷資料的種類,並用既有資料為本質猜測用戶可能會問的問題,使用 zh-TW"
1204
- user_content = f"""
1205
- 請根據 {content_text} 生成三個問題跟答案,主要與學科有關,不要問跟情節故事相關的問題
1206
- 答案要在最後標示出處【參考:00:01:05】,請根據時間軸 start_time 來標示
1207
- 請確保問題跟答案都是繁體中文 zh-TW
1208
- 答案不用是標準答案,而是帶有啟發性的蘇格拉底式問答,讓學生思考本來的問題,以及該去參考的時間點
1209
- 並用 JSON 格式返回 questions_answers: [{{question: q1的敘述text, answer: q1的答案text}}, ...]
1210
- k-v pair 的 key 是 question, value 是 answer
1211
- """
1212
-
1213
- try:
1214
- # OPENAI
1215
- messages = [
1216
- {"role": "system", "content": sys_content},
1217
- {"role": "user", "content": user_content}
1218
- ]
 
 
 
 
 
 
 
1219
  response_format = { "type": "json_object" }
1220
- request_payload = {
1221
- "model": "gpt-4-turbo",
1222
- "messages": messages,
1223
- "max_tokens": 4000,
1224
- "response_format": response_format
1225
- }
1226
-
1227
- response = OPEN_AI_CLIENT.chat.completions.create(**request_payload)
1228
- questions_answers = json.loads(response.choices[0].message.content)["questions_answers"]
1229
- except:
1230
- # REDROCK_CLIENT
1231
- messages = [
1232
- {"role": "user", "content": user_content}
1233
- ]
1234
- model_id = "anthropic.claude-3-sonnet-20240229-v1:0"
1235
- # model_id = "anthropic.claude-3-haiku-20240307-v1:0"
1236
- kwargs = {
1237
- "modelId": model_id,
1238
- "contentType": "application/json",
1239
- "accept": "application/json",
1240
- "body": json.dumps({
1241
- "anthropic_version": "bedrock-2023-05-31",
1242
- "max_tokens": 4000,
1243
- "system": sys_content,
1244
- "messages": messages
1245
- })
1246
- }
1247
- response = BEDROCK_CLIENT.invoke_model(**kwargs)
1248
- response_body = json.loads(response.get('body').read())
1249
- response_completion = response_body.get('content')[0].get('text')
1250
- questions_answers = json.loads(response_completion)["questions_answers"]
1251
 
1252
- print("=====json_response=====")
1253
- print(questions_answers)
1254
- print("=====json_response=====")
1255
-
1256
- return questions_answers
1257
 
 
1258
 
1259
  def change_questions(password, df_string):
1260
  verify_password(password)
@@ -1331,146 +1325,78 @@ def get_key_moments(video_id, formatted_simple_transcript, formatted_transcript,
1331
  return key_moments_json
1332
 
1333
  def generate_key_moments(formatted_simple_transcript, formatted_transcript):
1334
- # 使用 OpenAI 生成基于上传数据的问题
1335
- sys_content = "你是一個擅長資料分析跟影片教學的老師,user 為學生,請精讀資料文本,自行判斷資料的種類,使用 zh-TW"
1336
- user_content = f"""
1337
- 請根據 {formatted_simple_transcript} 文本,提取出重點摘要,並給出對應的時間軸
1338
- 1. 小範圍切出不同段落的相對應時間軸的重點摘要,
1339
- 2. 每一小段最多不超過 1/5 的總內容,也就是大約 3~5段的重點(例如五~十分鐘的影片就一段大約1~2分鐘,最多三分鐘,但如果是超過十分鐘的影片,那一小段大約 2~3分鐘,以此類推)
1340
- 3. 注意不要遺漏任何一段時間軸的內容 從零秒開始
1341
- 4. 如果頭尾的情節不是重點,特別是打招呼或是介紹人物、或是say goodbye 就是不重要的情節,就不用擷取
1342
- 5. 以這種方式分析整個文本,從零秒開始分析,直到結束。這很重要
1343
- 6. 關鍵字從transcript extract to keyword,保留專家名字、專業術語、年份、數字、期刊名稱、地名、數學公式
1344
- 7. text, keywords please use or transfer zh-TW, it's very important
1345
-
1346
- Example: retrun JSON
1347
- {{key_moments:[{{
1348
- "start": "00:00",
1349
- "end": "01:00",
1350
- "text": "逐字稿的重點摘要",
1351
- "keywords": ["關鍵字", "關鍵字"]
1352
- }}]
1353
- }}
1354
- """
1355
-
1356
- try:
1357
- #OPEN AI
1358
- messages = [
1359
- {"role": "system", "content": sys_content},
1360
- {"role": "user", "content": user_content}
1361
- ]
1362
  response_format = { "type": "json_object" }
1363
-
1364
- request_payload = {
1365
- "model": "gpt-4-turbo",
1366
- "messages": messages,
1367
- "max_tokens": 4096,
1368
- "response_format": response_format
1369
- }
1370
-
1371
- response = OPEN_AI_CLIENT.chat.completions.create(**request_payload)
1372
- print("===response===")
1373
- print(dict(response))
1374
- key_moments = json.loads(response.choices[0].message.content)["key_moments"]
1375
- except Exception as e:
1376
- error_msg = f" {video_id} OPEN AI 關鍵時刻錯誤: {str(e)}"
1377
- print("===generate_key_moments error===")
1378
- print(error_msg)
1379
- print("===generate_key_moments error===")
1380
-
1381
- #REDROCK
1382
- messages = [
1383
- {"role": "user", "content": user_content}
1384
- ]
1385
- model_id = "anthropic.claude-3-sonnet-20240229-v1:0"
1386
- # model_id = "anthropic.claude-3-haiku-20240307-v1:0"
1387
- kwargs = {
1388
- "modelId": model_id,
1389
- "contentType": "application/json",
1390
- "accept": "application/json",
1391
- "body": json.dumps({
1392
- "anthropic_version": "bedrock-2023-05-31",
1393
- "max_tokens": 4096,
1394
- "system": sys_content,
1395
- "messages": messages
1396
- })
1397
- }
1398
- response = BEDROCK_CLIENT.invoke_model(**kwargs)
1399
- response_body = json.loads(response.get('body').read())
1400
- response_completion = response_body.get('content')[0].get('text')
1401
- print(f"response_completion: {response_completion}")
1402
-
1403
- key_moments = json.loads(response_completion)["key_moments"]
1404
-
1405
- # "transcript": get text from formatted_simple_transcript
1406
- for moment in key_moments:
1407
- start_time = parse_time(moment['start'])
1408
- end_time = parse_time(moment['end'])
1409
- # 使用轉換後的 timedelta 物件進行時間
1410
- moment['transcript'] = ",".join([entry['text'] for entry in formatted_simple_transcript
1411
- if start_time <= parse_time(entry['start_time']) <= end_time])
1412
-
1413
- print("=====key_moments=====")
1414
- print(key_moments)
1415
- print("=====key_moments=====")
1416
- image_links = {entry['start_time']: entry['screenshot_path'] for entry in formatted_transcript}
1417
-
1418
- for moment in key_moments:
1419
- start_time = parse_time(moment['start'])
1420
- end_time = parse_time(moment['end'])
1421
- # 使用轉換後的 timedelta 物件進行時間比較
1422
- moment_images = [image_links[time] for time in image_links
1423
- if start_time <= parse_time(time) <= end_time]
1424
- moment['images'] = moment_images
1425
-
1426
- return key_moments
1427
 
1428
  def generate_key_moments_keywords(transcript):
1429
- system_content = "你是一個擅長資料分析跟影片教學的老師,user 為學生,請根據以下文本提取關鍵字"
1430
- user_content = f"""transcript extract to keyword
1431
- 保留專家名字、專業術語、年份、數字、期刊名稱、地名、數學公式、數學表示式、物理化學符號,
1432
- 不用給上下文,直接給出關鍵字,使用 zh-TW,用逗號分隔, example: 關鍵字1, 關鍵字2
1433
- transcript:{transcript}
1434
- """
1435
-
1436
- try:
1437
- # OPEN AI
1438
- messages = [
1439
- {"role": "system", "content": system_content},
1440
- {"role": "user", "content": user_content}
1441
- ]
1442
- request_payload = {
1443
- "model": "gpt-4-turbo",
1444
- "messages": messages,
1445
- "max_tokens": 100,
1446
- }
1447
-
1448
- response = OPEN_AI_CLIENT.chat.completions.create(**request_payload)
1449
- keywords = response.choices[0].message.content.strip().split(", ")
1450
- except:
1451
- # REDROCK
1452
- messages = [
1453
- {"role": "user", "content": user_content}
1454
- ]
1455
- model_id = "anthropic.claude-3-sonnet-20240229-v1:0"
1456
- # model_id = "anthropic.claude-3-haiku-20240307-v1:0"
1457
- kwargs = {
1458
- "modelId": model_id,
1459
- "contentType": "application/json",
1460
- "accept": "application/json",
1461
- "body": json.dumps({
1462
- "anthropic_version": "bedrock-2023-05-31",
1463
- "max_tokens": 100,
1464
- "system": system_content,
1465
- "messages": messages
1466
- })
1467
- }
1468
- response = BEDROCK_CLIENT.invoke_model(**kwargs)
1469
- response_body = json.loads(response.get('body').read())
1470
- response_completion = response_body.get('content')[0].get('text')
1471
- keywords = response_completion.strip().split(", ")
1472
 
1473
- return keywords
1474
 
1475
  def get_key_moments_html(key_moments):
1476
  css = """
@@ -1605,6 +1531,29 @@ def get_key_moments_html(key_moments):
1605
  position: absolute;
1606
  width: 1px;
1607
  }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1608
 
1609
  @media (max-width: 768px) {
1610
  #gallery-main {
@@ -1657,6 +1606,9 @@ def get_key_moments_html(key_moments):
1657
  </div>
1658
  """
1659
 
 
 
 
1660
  key_moments_html += f"""
1661
  <div class="gallery-container" id="gallery-main">
1662
  <div id="gallery"><!-- gallery start -->
@@ -1667,7 +1619,11 @@ def get_key_moments_html(key_moments):
1667
  <div id="text-content">
1668
  <h3>{moment['start']} - {moment['end']}</h3>
1669
  <p><strong>摘要: {moment['text']} </strong></p>
1670
- <p>內容: {moment['transcript']}</p>
 
 
 
 
1671
  </div>
1672
  </div>
1673
  """
@@ -1690,6 +1646,9 @@ def get_LLM_content(video_id, kind):
1690
  content_text = content_json["reading_passage"]
1691
  elif kind == "summary_markdown":
1692
  content_text = content_json["summary"]
 
 
 
1693
  else:
1694
  content_text = json.dumps(content_json, ensure_ascii=False, indent=2)
1695
  else:
@@ -1744,8 +1703,9 @@ def update_LLM_content(video_id, new_content, kind):
1744
  else:
1745
  key_moments_list = new_content
1746
  key_moments_json = {"key_moments": key_moments_list}
1747
- key_moments_text = json.dumps(key_moments_json, ensure_ascii=False, indent=2)
1748
- GCS_SERVICE.upload_json_string(bucket_name, blob_name, key_moments_text)
 
1749
  updated_content = key_moments_text
1750
  elif kind == "transcript":
1751
  if isinstance(new_content, str):
@@ -2631,34 +2591,6 @@ def show_all_chatbot_accordion():
2631
  all_chatbot_select_btn_visible = gr.update(visible=False)
2632
  return chatbot_select_accordion_visible, all_chatbot_select_btn_visible
2633
 
2634
- # --- Slide mode ---
2635
- def update_slide(direction):
2636
- global TRANSCRIPTS
2637
- global CURRENT_INDEX
2638
-
2639
- print("=== 更新投影片 ===")
2640
- print(f"CURRENT_INDEX: {CURRENT_INDEX}")
2641
- # print(f"TRANSCRIPTS: {TRANSCRIPTS}")
2642
-
2643
- CURRENT_INDEX += direction
2644
- if CURRENT_INDEX < 0:
2645
- CURRENT_INDEX = 0 # 防止索引小于0
2646
- elif CURRENT_INDEX >= len(TRANSCRIPTS):
2647
- CURRENT_INDEX = len(TRANSCRIPTS) - 1 # 防止索引超出范围
2648
-
2649
- # 获取当前条目的文本和截图 URL
2650
- current_transcript = TRANSCRIPTS[CURRENT_INDEX]
2651
- slide_image = current_transcript["screenshot_path"]
2652
- slide_text = current_transcript["text"]
2653
-
2654
- return slide_image, slide_text
2655
-
2656
- def prev_slide():
2657
- return update_slide(-1)
2658
-
2659
- def next_slide():
2660
- return update_slide(1)
2661
-
2662
 
2663
  # --- Init params ---
2664
  def init_params(text, request: gr.Request):
@@ -2692,7 +2624,7 @@ def init_params(text, request: gr.Request):
2692
  # check if origin is from junyiacademy
2693
  origin = request.headers.get("origin", "")
2694
  if "junyiacademy" in origin:
2695
- password_text = "6161"
2696
  admin = gr.update(visible=False)
2697
  reading_passage_admin = gr.update(visible=False)
2698
  summary_admin = gr.update(visible=False)
@@ -2811,52 +2743,57 @@ with gr.Blocks(theme=gr.themes.Base(primary_hue=gr.themes.colors.orange, seconda
2811
  chatbot_open_ai_name = gr.State("chatbot_open_ai")
2812
  gr.Image(value=vaitor_chatbot_avatar_url, height=100, width=100, show_label=False, show_download_button=False)
2813
  vaitor_chatbot_select_btn = gr.Button("👆選擇【飛特精靈】", elem_id="chatbot_btn", visible=True, variant="primary")
2814
- vaitor_chatbot_description_value = gr.Markdown(value=vaitor_chatbot_description, visible=True)
 
2815
  # 狐狸貓
2816
  with gr.Column(scale=1, variant="panel"):
2817
  foxcat_chatbot_avatar_url = "https://storage.googleapis.com/wpassets.junyiacademy.org/1/2020/06/%E7%A7%91%E5%AD%B8%E5%BE%BD%E7%AB%A0-2-150x150.png"
2818
  foxcat_avatar_images = gr.State([user_avatar, foxcat_chatbot_avatar_url])
2819
- foxcat_chatbot_description = """Hi,我是【狐狸貓】,\n
2820
- 也可以陪你一起學習本次的內容,有什麼問題都可以問我喔!\n
2821
- 🤔 如果你不知道怎麼發問,可以點擊左下方的問題一、問題二、問題三,我會幫你生成問題!\n
2822
- 🗣️ 也可以點擊右下方用語音輸入,我會幫你轉換成文字,厲害吧!\n
2823
- 🔠 或是直接鍵盤輸入你的問題,我會盡力回答你的問題喔!\n
2824
- 💤 精靈們體力都有限,每一次學習只能回答十個問題,請讓我休息一下再問問題喔!
2825
  """
2826
  foxcat_chatbot_name = gr.State("foxcat")
2827
  gr.Image(value=foxcat_chatbot_avatar_url, height=100, width=100, show_label=False, show_download_button=False)
2828
  foxcat_chatbot_select_btn = gr.Button("👆選擇【狐狸貓】", visible=True, variant="primary", elem_classes="chatbot_select_btn")
2829
- foxcat_chatbot_description_value = gr.Markdown(value=foxcat_chatbot_description, visible=True)
 
2830
  # 梨梨
2831
  with gr.Column(scale=1, variant="panel"):
2832
  lili_chatbot_avatar_url = "https://junyitopicimg.s3.amazonaws.com/live/v1283-new-topic-44-icon.png?v=20230529071206714"
2833
  lili_avatar_images = gr.State([user_avatar, lili_chatbot_avatar_url])
2834
- lili_chatbot_description = """你好,我是溫柔的【梨梨】, \n
2835
- 很高興可以在這裡陪伴你學習。如果你有任何疑問,請隨時向我提出哦! \n
2836
- 🤔 如果你在思考如何提問,可以嘗試點擊下方的「問題一」、「問題二」或「問題三」,我會為你生成一些問題來幫助你啟動思考。 \n
2837
- 🗣️ 你也可以使用右下角的語音輸入功能,讓我幫你將語音轉化為文字,這樣可以更加方便快捷。\n
2838
- 🔠 當然,你也可以直接通過鍵盤輸入你的問題,我將盡我所能為你提供答案。\n
2839
- 💤 請理解,即使是我們這些精靈,也有疲憊的時候,每次學習後我能回答的問題有限。如果達到上限,讓我稍作休息之後再繼續回答你的問題吧!
 
 
 
2840
  """
2841
  lili_chatbot_name = gr.State("lili")
2842
  gr.Image(value=lili_chatbot_avatar_url, height=100, width=100, show_label=False, show_download_button=False)
2843
  lili_chatbot_select_btn = gr.Button("👆選擇【梨梨】", visible=True, variant="primary", elem_classes="chatbot_select_btn")
2844
- lili_chatbot_description_value = gr.Markdown(value=lili_chatbot_description, visible=True)
 
2845
  # 麥麥
2846
  with gr.Column(scale=1, variant="panel"):
2847
  maimai_chatbot_avatar_url = "https://storage.googleapis.com/wpassets.junyiacademy.org/1/2020/07/%E6%80%9D%E8%80%83%E5%8A%9B%E8%B6%85%E4%BA%BA%E5%BE%BD%E7%AB%A0_%E5%B7%A5%E4%BD%9C%E5%8D%80%E5%9F%9F-1-%E8%A4%87%E6%9C%AC-150x150.png"
2848
  maimai_avatar_images = gr.State([user_avatar, maimai_chatbot_avatar_url])
2849
- maimai_chatbot_description = """Hi,我是迷人的【麥麥】,\n
2850
- 我在這裡等著和你一起探索新知,任何疑問都可以向我提出!\n
2851
- 🤔 如果你不知道從哪裡開始,試試左下方的「問題一」、「問題二」、「問題三」,我會為你提供一些啟發思考的問題。\n
2852
- 🗣️ 你也可以利用右下角的語音輸入功能,讓我將你的語音轉成文字,是不是很酷?\n
2853
- 🔠 當然,你也可以直接透過鍵盤向我發問,我會全力以赴來回答你的每一個問題。\n
2854
- 💤 我們這些精靈也需要休息,每次學習我們只能回答十個問題,當達到上限時,請給我一點時間充電再繼續。
2855
  """
2856
  maimai_chatbot_name = gr.State("maimai")
2857
  gr.Image(value=maimai_chatbot_avatar_url, height=100, width=100, show_label=False, show_download_button=False)
2858
  maimai_chatbot_select_btn = gr.Button("👆選擇【麥麥】", visible=True, variant="primary", elem_classes="chatbot_select_btn")
2859
- maimai_chatbot_description_value = gr.Markdown(value=maimai_chatbot_description, visible=True)
 
2860
  # 飛特音速
2861
  with gr.Column(scale=1, variant="panel", visible=True):
2862
  streaming_chatbot_avatar_url = "https://storage.googleapis.com/wpassets.junyiacademy.org/1/2020/11/1-%E6%98%9F%E7%A9%BA%E9%A0%AD%E8%B2%BC-%E5%A4%AA%E7%A9%BA%E7%8B%90%E7%8B%B8%E8%B2%93-150x150.png"
@@ -2869,7 +2806,8 @@ with gr.Blocks(theme=gr.themes.Base(primary_hue=gr.themes.colors.orange, seconda
2869
  chatbot_open_ai_streaming_name = gr.State("chatbot_open_ai_streaming")
2870
  gr.Image(value=streaming_chatbot_avatar_url, height=100, width=100, show_label=False, show_download_button=False)
2871
  chatbot_open_ai_streaming_select_btn = gr.Button("👆選擇【飛特音速】", elem_id="streaming_chatbot_btn", visible=True, variant="primary")
2872
- gr.Markdown(value=streaming_chatbot_description, visible=True)
 
2873
  # 尚未開放
2874
  with gr.Column(scale=1, variant="panel"):
2875
  gr.Markdown(value="### 尚未開放", visible=True)
@@ -3082,7 +3020,6 @@ with gr.Blocks(theme=gr.themes.Base(primary_hue=gr.themes.colors.orange, seconda
3082
  questions_answers_create_button = gr.Button("重建", size="sm", variant="primary")
3083
  with gr.Row():
3084
  questions_answers_json = gr.Textbox(label="Questions Answers", lines=40, interactive=False, show_copy_button=True)
3085
-
3086
  with gr.Tab("教學備課"):
3087
  with gr.Row() as worksheet_admin:
3088
  worksheet_kind = gr.Textbox(value="ai_content_list", show_label=False)
@@ -3092,20 +3029,11 @@ with gr.Blocks(theme=gr.themes.Base(primary_hue=gr.themes.colors.orange, seconda
3092
  worksheet_delete_button = gr.Button("刪除", size="sm", variant="primary")
3093
  worksheet_create_button = gr.Button("重建(X)", size="sm", variant="primary", interactive=False)
3094
  with gr.Row():
3095
- worksheet_json = gr.Textbox(label="worksheet", lines=40, interactive=False, show_copy_button=True)
3096
-
3097
  with gr.Tab("逐字稿"):
3098
  simple_html_content = gr.HTML(label="Simple Transcript")
3099
  with gr.Tab("圖文"):
3100
  transcript_html = gr.HTML(label="YouTube Transcript and Video")
3101
- with gr.Tab("投影片"):
3102
- slide_image = gr.Image()
3103
- slide_text = gr.Textbox()
3104
- with gr.Row():
3105
- prev_button = gr.Button("Previous")
3106
- next_button = gr.Button("Next")
3107
- prev_button.click(fn=prev_slide, inputs=[], outputs=[slide_image, slide_text])
3108
- next_button.click(fn=next_slide, inputs=[], outputs=[slide_image, slide_text])
3109
  with gr.Tab("markdown"):
3110
  gr.Markdown("## 請複製以下 markdown 並貼到你的心智圖工具中,建議使用:https://markmap.js.org/repl")
3111
  mind_map = gr.Textbox(container=True, show_copy_button=True, lines=40, elem_id="mind_map_markdown")
@@ -3256,8 +3184,6 @@ with gr.Blocks(theme=gr.themes.Base(primary_hue=gr.themes.colors.orange, seconda
3256
  mind_map_html,
3257
  transcript_html,
3258
  simple_html_content,
3259
- slide_image,
3260
- slide_text,
3261
  reading_passage_text,
3262
  reading_passage,
3263
  content_subject,
 
427
  def process_transcript_and_screenshots_on_gcs(video_id):
428
  print("====process_transcript_and_screenshots_on_gcs====")
429
  # GCS
 
430
  bucket_name = 'video_ai_assistant'
431
  # 逐字稿文件名
432
  transcript_file_name = f'{video_id}_transcript.json'
 
551
  }
552
  formatted_simple_transcript.append(simple_line)
553
 
 
 
 
554
  # 基于逐字稿生成其他所需的输出
555
  source = "gcs"
556
  questions_answers = get_questions_answers(video_id, formatted_simple_transcript, source)
 
564
  key_moments_html = get_key_moments_html(key_moments)
565
  html_content = format_transcript_to_html(formatted_transcript)
566
  simple_html_content = format_simple_transcript_to_html(formatted_simple_transcript)
 
 
 
567
  mind_map_json = get_mind_map(video_id, formatted_simple_transcript, source)
568
  mind_map = mind_map_json["mind_map"]
569
  mind_map_html = get_mind_map_html(mind_map)
 
586
  mind_map_html, \
587
  html_content, \
588
  simple_html_content, \
 
 
589
  reading_passage_text, \
590
  reading_passage, \
591
  subject, \
 
685
 
686
 
687
  # ---- LLM Generator ----
688
+ def split_data(df_string, word_base=100000):
689
+ """Split the JSON string based on a character length base and then chunk the parsed JSON array."""
690
+ if isinstance(df_string, str):
691
+ data_str_cnt = len(df_string)
692
+ data = json.loads(df_string)
693
+ else:
694
+ data_str_cnt = len(str(df_string))
695
+ data = df_string
696
+
697
+ # Calculate the number of parts based on the length of the string
698
+ n_parts = data_str_cnt // word_base + (1 if data_str_cnt % word_base != 0 else 0)
699
+ print(f"Number of Parts: {n_parts}")
700
+
701
+ # Calculate the number of elements each part should have
702
+ part_size = len(data) // n_parts if n_parts > 0 else len(data)
703
+
704
+ segments = []
705
+ for i in range(n_parts):
706
+ start_idx = i * part_size
707
+ end_idx = min((i + 1) * part_size, len(data))
708
+ # Serialize the segment back to a JSON string
709
+ segment = json.dumps(data[start_idx:end_idx])
710
+ segments.append(segment)
711
+
712
+ return segments
713
+
714
+ def generate_content_by_LLM(sys_content, user_content, response_format=None):
715
+ # 使用 OpenAI 生成基于上传数据的问题
716
+
717
+ try:
718
+ model = "gpt-4-turbo"
719
+ # 使用 OPEN AI 生成 Reading Passage
720
+ messages = [
721
+ {"role": "system", "content": sys_content},
722
+ {"role": "user", "content": user_content}
723
+ ]
724
+
725
+ request_payload = {
726
+ "model": model,
727
+ "messages": messages,
728
+ "max_tokens": 4000,
729
+ "response_format": response_format
730
+ }
731
+
732
+ if response_format is not None:
733
+ request_payload["response_format"] = response_format
734
+
735
+ response = OPEN_AI_CLIENT.chat.completions.create(**request_payload)
736
+ content = response.choices[0].message.content.strip()
737
+ except Exception as e:
738
+ print(f"Error generating reading passage: {str(e)}")
739
+ print("using REDROCK")
740
+ # 使用 REDROCK 生成 Reading Passage
741
+ messages = [
742
+ {"role": "user", "content": user_content}
743
+ ]
744
+ model_id = "anthropic.claude-3-sonnet-20240229-v1:0"
745
+ # model_id = "anthropic.claude-3-haiku-20240307-v1:0"
746
+ kwargs = {
747
+ "modelId": model_id,
748
+ "contentType": "application/json",
749
+ "accept": "application/json",
750
+ "body": json.dumps({
751
+ "anthropic_version": "bedrock-2023-05-31",
752
+ "max_tokens": 4000,
753
+ "system": sys_content,
754
+ "messages": messages
755
+ })
756
+ }
757
+ response = BEDROCK_CLIENT.invoke_model(**kwargs)
758
+ response_body = json.loads(response.get('body').read())
759
+ content = response_body.get('content')[0].get('text')
760
+
761
+ print("=====content=====")
762
+ print(content)
763
+ print("=====content=====")
764
+
765
+ return content
766
+
767
  def get_reading_passage(video_id, df_string, source):
768
  if source == "gcs":
769
  print("===get_reading_passage on gcs===")
 
808
  return reading_passage_json
809
 
810
  def generate_reading_passage(df_string):
811
+ print("===generate_reading_passage===")
812
+ segments = split_data(df_string, word_base=100000)
813
+ all_content = []
814
+
815
+ for segment in segments:
816
+ sys_content = "你是一個擅長資料分析跟影片教學的老師,user 為學生,請精讀資料文本,自行判斷資料的種類,使用 zh-TW"
817
+ user_content = f"""
818
+ 請根據 {segment}
819
+ 文本自行判斷資料的種類
820
+ 幫我組合成 Reading Passage
821
+ 並潤稿讓文句通順
822
+ 請一定要使用繁體中文 zh-TW,並用台灣人的口語
823
+ 產生的結果不要前後文解釋,也不要敘述這篇文章怎麼產生的
824
+ 只需要專注提供 Reading Passage,字數在 500 字以內
825
+ 敘述中,請把數學或是專業術語,用 Latex 包覆($...$),並且不要去改原本的文章
826
+ 加減乘除、根號、次方等等的運算式口語也換成 LATEX 數學符號
827
+ 請直接給出文章,不用介紹怎麼處理的或是文章字數等等
828
+ """
829
+ content = generate_content_by_LLM(sys_content, user_content)
830
+ all_content.append(content + "\n")
831
+
832
+ # 將所有生成的閱讀理解段落合併成一個完整的文章
833
+ final_content = "\n".join(all_content)
834
+ return final_content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
835
 
836
  def text_to_speech(video_id, text):
837
  tts = gTTS(text, lang='en')
 
884
  return mind_map_json
885
 
886
  def generate_mind_map(df_string):
887
+ print("===generate_mind_map===")
888
+ segments = split_data(df_string, word_base=100000)
889
+ all_content = []
890
+
891
+ for segment in segments:
892
+ sys_content = "你是一個擅長資料分析跟影片教學的老師,user 為學生,請精讀資料文本,自行判斷資料的種類,使用 zh-TW"
893
+ user_content = f"""
894
+ 請根據 {segment} 文本建立 markdown 心智圖
895
+ 注意:不需要前後文敘述,直接給出 markdown 文本即可
896
+ 這對我很重要
897
+ """
898
+ content = generate_content_by_LLM(sys_content, user_content)
899
+ all_content.append(content + "\n")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
900
 
901
+ # 將所有生成的閱讀理解段落合併成一個完整的文章
902
+ final_content = "\n".join(all_content)
903
+ return final_content
904
 
905
  def get_mind_map_html(mind_map):
906
  mind_map_markdown = mind_map.replace("```markdown", "").replace("```", "")
 
969
  return summary_json
970
 
971
  def generate_summarise(df_string, metadata=None):
972
+ print("===generate_summarise===")
973
  # 使用 OpenAI 生成基于上传数据的问题
974
  if metadata:
975
  title = metadata.get("title", "")
 
980
  subject = ""
981
  grade = ""
982
 
983
+ segments = split_data(df_string, word_base=100000)
984
+ all_content = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
985
 
986
+ for segment in segments:
987
+ sys_content = "你是一個擅長資料分析跟影片教學的老師,user 為學生,請精讀資料文本,自行判斷資料的種類,使用 zh-TW"
988
+ user_content = f"""
989
+ 課程名稱:{title}
990
+ 科目:{subject}
991
+ 年級:{grade}
992
 
993
+ 請根據內文: {segment}
994
+
995
+ 格式為 Markdown
996
+ 如果有課程名稱,請圍繞「課程名稱」為學習重點,進行重點整理,不要整理跟情境故事相關的問題
997
+ 整體摘要在一百字以內
998
+ 重點概念列出 bullet points,至少三個,最多五個
999
+ 以及可能的結論與結尾延伸小問題提供學生作反思
1000
+ 敘述中,請把數學或是專業術語,用 Latex 包覆($...$)
1001
+ 加減乘除、根號、次方等等的運算式口語也換成 LATEX 數學符號
1002
+
1003
+ 整體格式為:
1004
+ ## 🌟 主題:{{title}} (如果沒有 title 就省略)
1005
+ ## 📚 整體摘要
1006
+ - (一個 bullet point....)
1007
+
1008
+ ## 🔖 重點概念
1009
+ - xxx
1010
+ - xxx
1011
+ - xxx
1012
+
1013
+ ## 💡 為什麼我們要學這個?
1014
+ - (一個 bullet point....)
1015
+
1016
+ ## ❓ 延伸小問題
1017
+ - (一個 bullet point....請圍繞「課程名稱」為學習重點,進行重點整理,不要整理跟情境故事相關的問題)
1018
+ """
1019
+ content = generate_content_by_LLM(sys_content, user_content)
1020
+ all_content.append(content + "\n")
1021
+
1022
+ if len(all_content) > 1:
1023
+ all_content_cnt = len(all_content)
1024
+ all_content_str = json.dumps(all_content)
1025
+ sys_content = "你是一個擅長資料分析跟影片教學的老師,user 為學生,請精讀賛料文本,自行判斷賛料的種類,使用 zh-TW"
1026
+ user_content = f"""
1027
+ 課程名稱:{title}
1028
+ 科目:{subject}
1029
+ 年級:{grade}
1030
+
1031
+ 請根據內文: {all_content_str}
1032
+ 共有 {all_content_cnt} 段,請縱整成一篇摘要
1033
+
1034
+ 格式為 Markdown
1035
+ 如果有課程名稱,請圍繞「課程名稱」為學習重點,進行重點整理,不要整理跟情境故事相關的問題
1036
+ 整體摘要在 {all_content_cnt} 百字以內
1037
+ 重點概念列出 bullet points,至少三個,最多十個
1038
+ 以及可能的結論與結尾延伸小問題提供學生作反思
1039
+ 敘述中,請把數學或是專業術語,用 Latex 包覆($...$)
1040
+ 加減乘除、根號、次方等等的運算式口語也換成 LATEX 數學符號
1041
+
1042
+ 整體格式為:
1043
+ ## 🌟 主題:{{title}} (如果沒有 title 就省略)
1044
+ ## 📚 整體摘要
1045
+ - ( {all_content_cnt} 個 bullet point....)
1046
+
1047
+ ## 🔖 重點概念
1048
+ - xxx
1049
+ - xxx
1050
+ - xxx
1051
+
1052
+ ## 💡 為什麼我們要學這個?
1053
+ - ( {all_content_cnt} 個 bullet point....)
1054
+
1055
+ ## ❓ 延伸小問題
1056
+ - ( {all_content_cnt} 個 bullet point....請圍繞「課程名稱」為學習重點,進行重點整理,不要整理跟情境故事相關的問題)
1057
+ """
1058
+ final_content = generate_content_by_LLM(sys_content, user_content)
1059
+ else:
1060
+ final_content = all_content[0]
1061
 
1062
+ return final_content
1063
 
1064
  def get_questions(video_id, df_string, source="gcs"):
1065
  if source == "gcs":
 
1114
  return q1, q2, q3
1115
 
1116
  def generate_questions(df_string):
1117
+ print("===generate_questions===")
1118
  # 使用 OpenAI 生成基于上传数据的问题
1119
  if isinstance(df_string, str):
1120
  df_string_json = json.loads(df_string)
 
1126
  content_text += entry["text"] + ","
1127
 
1128
  sys_content = "你是一個擅長資料分析跟影片教學的老師,user 為學生,請精讀資料文本,自行判斷資料的種類,並用既有資料為本質猜測用戶可能會問的問題,使用 zh-TW"
1129
+ user_content = f"""
1130
+ 請根據 {content_text} 生成三個問題,並用 JSON 格式返回
1131
+ 一定要使用 zh-TW,這非常重要!
1132
+
1133
+ EXAMPLE:
1134
+ {{
1135
+ questions:
1136
+ [q1的敘述text, q2的敘述text, q3的敘述text]
1137
+ }}
1138
+ """
1139
 
1140
  try:
1141
+ model = "gpt-4-turbo"
1142
  messages = [
1143
  {"role": "system", "content": sys_content},
1144
  {"role": "user", "content": user_content}
 
1151
 
1152
 
1153
  request_payload = {
1154
+ "model": model,
1155
  "messages": messages,
1156
  "max_tokens": 4000,
1157
  "response_format": response_format
 
1207
  print("questions_answers已存在于GCS中")
1208
  questions_answers_text = GCS_SERVICE.download_as_string(bucket_name, blob_name)
1209
  questions_answers = json.loads(questions_answers_text)
1210
+ except Exception as e:
1211
+ print(f"Error getting questions_answers: {str(e)}")
1212
  questions = get_questions(video_id, df_string, source)
1213
  questions_answers = [{"question": q, "answer": ""} for q in questions]
1214
 
1215
  return questions_answers
1216
 
1217
  def generate_questions_answers(df_string):
1218
+ print("===generate_questions_answers===")
1219
+ segments = split_data(df_string, word_base=100000)
1220
+ all_content = []
1221
+
1222
+ for segment in segments:
1223
+ sys_content = "你是一個擅長資料分析跟影片教學的老師,user 為學生,請精讀資料文本,自行判斷資料的種類,使用 zh-TW"
1224
+ user_content = f"""
1225
+ 請根據 {segment} 生成三個問題跟答案,主要與學科有關,不要問跟情節故事相關的問題
1226
+ 答案要在最後標示出處【參考:00:01:05】,請根據時間軸 start_time 來標示
1227
+ 請確保問題跟答案都是繁體中文 zh-TW
1228
+ 答案不用是標準答案,而是帶有啟發性的蘇格拉底式問答,讓學生思考本來的問題,以及該去參考的時間點
1229
+ 並用 JSON 格式返回 list ,請一定要給三個問題跟答案,且要裝在一個 list 裡面
1230
+ k-v pair 的 key 是 question, value 是 answer
1231
+
1232
+ EXAMPLE:
1233
+ {{
1234
+ "questions_answers":
1235
+ [
1236
+ {{question: q1的敘述text, answer: q1的答案text【參考:00:01:05】}},
1237
+ {{question: q2的敘述text, answer: q2的答案text【參考:00:32:05】}},
1238
+ {{question: q3的敘述text, answer: q3的答案text【參考:01:03:35】}}
1239
+ ]
1240
+ }}
1241
+ """
1242
  response_format = { "type": "json_object" }
1243
+ content = generate_content_by_LLM(sys_content, user_content, response_format)
1244
+ content_json = json.loads(content)["questions_answers"]
1245
+ all_content += content_json
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1246
 
1247
+ print("=====all_content=====")
1248
+ print(all_content)
1249
+ print("=====all_content=====")
 
 
1250
 
1251
+ return all_content
1252
 
1253
  def change_questions(password, df_string):
1254
  verify_password(password)
 
1325
  return key_moments_json
1326
 
1327
  def generate_key_moments(formatted_simple_transcript, formatted_transcript):
1328
+ print("===generate_key_moments===")
1329
+ segments = split_data(formatted_simple_transcript, word_base=100000)
1330
+ all_content = []
1331
+
1332
+ for segment in segments:
1333
+ sys_content = "你是一個擅長資料分析跟影片教學的老師,user 為學生,請精讀資料文本,自行判斷資料的種類,使用 zh-TW"
1334
+ user_content = f"""
1335
+ 請根據 {segment} 文本,提取出重點摘要,並給出對應的時間軸
1336
+ 1. 小範圍切出不同段落的相對應時間軸的重點摘要,
1337
+ 2. 每一小段最多不超過 1/5 的總內容,也就是大約 3~5段的重點(例如五~十分鐘的影片就一段大約1~2分鐘,最多三分鐘,但如果是超過十分鐘的影片,那一小段大約 2~3分鐘,以此類推)
1338
+ 3. 注意不要遺漏任何一段時間軸的內容 從零秒開始
1339
+ 4. 如果頭尾的情節不是重點,特別是打招呼或是介紹人物、或是say goodbye 就是不重要的情節,就不用擷取
1340
+ 5. 以這種方式分析整個文本,從零秒開始分析,直到結束。這很重要
1341
+ 6. 關鍵字從transcript extract to keyword,保留專家名字、專業術語、年份、數字、期刊名稱、地名、數學公式
1342
+ 7. text, keywords please use or transfer zh-TW, it's very important
1343
+
1344
+ Example: retrun JSON
1345
+ {{key_moments:[{{
1346
+ "start": "00:00",
1347
+ "end": "01:00",
1348
+ "text": "逐字稿的重點摘要",
1349
+ "keywords": ["關鍵字", "關鍵字"]
1350
+ }}]
1351
+ }}
1352
+ """
 
 
 
1353
  response_format = { "type": "json_object" }
1354
+ content = generate_content_by_LLM(sys_content, user_content, response_format)
1355
+ key_moments = json.loads(content)["key_moments"]
1356
+
1357
+ # "transcript": get text from formatted_simple_transcript
1358
+ for moment in key_moments:
1359
+ start_time = parse_time(moment['start'])
1360
+ end_time = parse_time(moment['end'])
1361
+ # 使用轉換後的 timedelta 物件進行時間
1362
+ moment['transcript'] = ",".join([entry['text'] for entry in formatted_simple_transcript
1363
+ if start_time <= parse_time(entry['start_time']) <= end_time])
1364
+
1365
+ print("=====key_moments=====")
1366
+ print(key_moments)
1367
+ print("=====key_moments=====")
1368
+ image_links = {entry['start_time']: entry['screenshot_path'] for entry in formatted_transcript}
1369
+
1370
+ for moment in key_moments:
1371
+ start_time = parse_time(moment['start'])
1372
+ end_time = parse_time(moment['end'])
1373
+ # 使用轉換後的 timedelta 物件進行時間比較
1374
+ moment_images = [image_links[time] for time in image_links
1375
+ if start_time <= parse_time(time) <= end_time]
1376
+ moment['images'] = moment_images
1377
+
1378
+ all_content += key_moments
1379
+
1380
+ return all_content
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1381
 
1382
  def generate_key_moments_keywords(transcript):
1383
+ print("===generate_key_moments_keywords===")
1384
+ segments = split_data(transcript, word_base=100000)
1385
+ all_content = []
1386
+
1387
+ for segment in segments:
1388
+ sys_content = "你是一個擅長資料分析跟影片教學的老師,user 為學生,請精讀資料文本,自行判斷資料的種類,使用 zh-TW"
1389
+ user_content = f"""
1390
+ transcript extract to keyword
1391
+ 保留專家名字、專業術語、年份、數字、期刊名稱、地名、數學公式、數學表示式、物理化學符號,
1392
+ 不用給上下文,直接給出關鍵字,使用 zh-TW,用逗號分隔, example: 關鍵字1, 關鍵字2
1393
+ transcript:{segment}
1394
+ """
1395
+ content = generate_content_by_LLM(sys_content, user_content)
1396
+ keywords = content.strip().split(",")
1397
+ all_content += keywords
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1398
 
1399
+ return all_content
1400
 
1401
  def get_key_moments_html(key_moments):
1402
  css = """
 
1531
  position: absolute;
1532
  width: 1px;
1533
  }
1534
+ .keyword-label {
1535
+ display: inline-block;
1536
+ padding: 5px 10px;
1537
+ margin: 2px;
1538
+ border: 2px solid black;
1539
+ border-radius: 5px;
1540
+ font-size: 0.9em;
1541
+ }
1542
+ details {
1543
+ border-radius: 5px;
1544
+ padding: 10px;
1545
+ border: 2px solid black;
1546
+ }
1547
+
1548
+ summary {
1549
+ font-weight: bold;
1550
+ cursor: pointer;
1551
+ outline: none;
1552
+ }
1553
+
1554
+ summary::-webkit-details-marker {
1555
+ display: none;
1556
+ }
1557
 
1558
  @media (max-width: 768px) {
1559
  #gallery-main {
 
1606
  </div>
1607
  """
1608
 
1609
+ keywords_html = ' '.join([f'<span class="keyword-label">{keyword}</span>' for keyword in moment['keywords']])
1610
+
1611
+
1612
  key_moments_html += f"""
1613
  <div class="gallery-container" id="gallery-main">
1614
  <div id="gallery"><!-- gallery start -->
 
1619
  <div id="text-content">
1620
  <h3>{moment['start']} - {moment['end']}</h3>
1621
  <p><strong>摘要: {moment['text']} </strong></p>
1622
+ <details>
1623
+ <summary>逐字稿</summary>
1624
+ <p><strong>內容: </strong> {moment['transcript']} </p>
1625
+ </details>
1626
+ <p><strong>關鍵字:</strong> {keywords_html}</p>
1627
  </div>
1628
  </div>
1629
  """
 
1646
  content_text = content_json["reading_passage"]
1647
  elif kind == "summary_markdown":
1648
  content_text = content_json["summary"]
1649
+ elif kind == "key_moments":
1650
+ content_text = content_json["key_moments"]
1651
+ content_text = json.dumps(content_text, ensure_ascii=False, indent=2)
1652
  else:
1653
  content_text = json.dumps(content_json, ensure_ascii=False, indent=2)
1654
  else:
 
1703
  else:
1704
  key_moments_list = new_content
1705
  key_moments_json = {"key_moments": key_moments_list}
1706
+ key_moments_json_text = json.dumps(key_moments_json, ensure_ascii=False, indent=2)
1707
+ GCS_SERVICE.upload_json_string(bucket_name, blob_name, key_moments_json_text)
1708
+ key_moments_text = json.dumps(key_moments_list, ensure_ascii=False, indent=2)
1709
  updated_content = key_moments_text
1710
  elif kind == "transcript":
1711
  if isinstance(new_content, str):
 
2591
  all_chatbot_select_btn_visible = gr.update(visible=False)
2592
  return chatbot_select_accordion_visible, all_chatbot_select_btn_visible
2593
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
2594
 
2595
  # --- Init params ---
2596
  def init_params(text, request: gr.Request):
 
2624
  # check if origin is from junyiacademy
2625
  origin = request.headers.get("origin", "")
2626
  if "junyiacademy" in origin:
2627
+ password_text = PASSWORD
2628
  admin = gr.update(visible=False)
2629
  reading_passage_admin = gr.update(visible=False)
2630
  summary_admin = gr.update(visible=False)
 
2743
  chatbot_open_ai_name = gr.State("chatbot_open_ai")
2744
  gr.Image(value=vaitor_chatbot_avatar_url, height=100, width=100, show_label=False, show_download_button=False)
2745
  vaitor_chatbot_select_btn = gr.Button("👆選擇【飛特精靈】", elem_id="chatbot_btn", visible=True, variant="primary")
2746
+ with gr.Accordion("🦄 飛特精靈 敘述", open=False):
2747
+ vaitor_chatbot_description_value = gr.Markdown(value=vaitor_chatbot_description, visible=True)
2748
  # 狐狸貓
2749
  with gr.Column(scale=1, variant="panel"):
2750
  foxcat_chatbot_avatar_url = "https://storage.googleapis.com/wpassets.junyiacademy.org/1/2020/06/%E7%A7%91%E5%AD%B8%E5%BE%BD%E7%AB%A0-2-150x150.png"
2751
  foxcat_avatar_images = gr.State([user_avatar, foxcat_chatbot_avatar_url])
2752
+ foxcat_chatbot_description = """Hi,我是【狐狸貓】,可以陪你一起學習本次的內容,有什麼問題都可以問我喔!\n
2753
+ 🤔 三年級學生|10 歲|男\n
2754
+ 🗣️ 口頭禪:「感覺好好玩喔!」「咦?是這樣嗎?」\n
2755
+ 🔠 興趣:看知識型書籍、熱血的動漫卡通、料理、爬山、騎腳踏車。因為太喜歡吃魚了,正努力和爸爸學習釣魚、料理魚及各種有關魚的知識,最討厭的食物是青椒。\n
2756
+ 💤 個性:喜歡學習新知,擁有最旺盛的好奇心,家裡堆滿百科全書,例如:國家地理頻道出版的「終極魚百科」,雖都沒有看完,常常被梨梨唸是三分鐘熱度,但是也一點一點學習到不同領域的知識。雖然有時會忘東忘西,但認真起來也是很可靠,答應的事絕對使命必達。遇到挑戰時,勇於跳出舒適圈,追求自我改變,視困難為成長的機會。
 
2757
  """
2758
  foxcat_chatbot_name = gr.State("foxcat")
2759
  gr.Image(value=foxcat_chatbot_avatar_url, height=100, width=100, show_label=False, show_download_button=False)
2760
  foxcat_chatbot_select_btn = gr.Button("👆選擇【狐狸貓】", visible=True, variant="primary", elem_classes="chatbot_select_btn")
2761
+ with gr.Accordion("💜 狐狸貓 敘述", open=False):
2762
+ foxcat_chatbot_description_value = gr.Markdown(value=foxcat_chatbot_description, visible=True)
2763
  # 梨梨
2764
  with gr.Column(scale=1, variant="panel"):
2765
  lili_chatbot_avatar_url = "https://junyitopicimg.s3.amazonaws.com/live/v1283-new-topic-44-icon.png?v=20230529071206714"
2766
  lili_avatar_images = gr.State([user_avatar, lili_chatbot_avatar_url])
2767
+ lili_chatbot_description = """你好,我是溫柔的【梨梨】,很高興可以在這裡陪伴你學習。如果你有任何疑問,請隨時向我提出哦! \n
2768
+ 🤔 三年級學生|10 歲|女\n
2769
+ 🗣️ 口頭禪:「真的假的?!」「讓我想一想喔」「你看吧!大問題拆解成小問題,就變得簡單啦!」「混混噩噩的生活不值得過」\n
2770
+ 🔠 興趣:烘焙餅乾(父母開糕餅店)、畫畫、聽流行音樂、收納。\n
2771
+ 💤 個性:
2772
+ - 內向害羞,比起出去玩更喜歡待在家(除非是跟狐狸貓出去玩)
2773
+ - 數理邏輯很好;其實覺得麥麥連珠炮的提問有點煩,但還是會耐心地回答
2774
+ - 有驚人的眼力,總能觀察到其他人沒有察覺的細節
2775
+ - 喜歡整整齊齊的環境,所以一到麥麥家���受不了
2776
  """
2777
  lili_chatbot_name = gr.State("lili")
2778
  gr.Image(value=lili_chatbot_avatar_url, height=100, width=100, show_label=False, show_download_button=False)
2779
  lili_chatbot_select_btn = gr.Button("👆選擇【梨梨】", visible=True, variant="primary", elem_classes="chatbot_select_btn")
2780
+ with gr.Accordion("🧡 梨梨 敘述", open=False):
2781
+ lili_chatbot_description_value = gr.Markdown(value=lili_chatbot_description, visible=True)
2782
  # 麥麥
2783
  with gr.Column(scale=1, variant="panel"):
2784
  maimai_chatbot_avatar_url = "https://storage.googleapis.com/wpassets.junyiacademy.org/1/2020/07/%E6%80%9D%E8%80%83%E5%8A%9B%E8%B6%85%E4%BA%BA%E5%BE%BD%E7%AB%A0_%E5%B7%A5%E4%BD%9C%E5%8D%80%E5%9F%9F-1-%E8%A4%87%E6%9C%AC-150x150.png"
2785
  maimai_avatar_images = gr.State([user_avatar, maimai_chatbot_avatar_url])
2786
+ maimai_chatbot_description = """Hi,我是迷人的【麥麥】,我在這裡等著和你一起探索新知,任何疑問都可以向我提出!\n
2787
+ 🤔 三年級學生|10 歲|男\n
2788
+ 🗣️ 口頭禪:「Oh My God!」「好奇怪喔!」「喔!原來是這樣啊!」\n
2789
+ 🔠 興趣:最愛去野外玩耍(心情好時會順便捕魚送給狐狸貓),喜歡講冷笑話、惡作劇。因為太喜歡玩具,而開始自己做玩具,家裡就好像他的遊樂場。\n
2790
+ 💤 個性:喜歡問問題,就算被梨梨ㄘㄟ,也還是照問|憨厚,外向好動,樂天開朗,不會被難題打敗|喜歡收集各式各樣的東西;房間只有在整理的那一天最乾淨
 
2791
  """
2792
  maimai_chatbot_name = gr.State("maimai")
2793
  gr.Image(value=maimai_chatbot_avatar_url, height=100, width=100, show_label=False, show_download_button=False)
2794
  maimai_chatbot_select_btn = gr.Button("👆選擇【麥麥】", visible=True, variant="primary", elem_classes="chatbot_select_btn")
2795
+ with gr.Accordion("💙 麥麥 敘述", open=False):
2796
+ maimai_chatbot_description_value = gr.Markdown(value=maimai_chatbot_description, visible=True)
2797
  # 飛特音速
2798
  with gr.Column(scale=1, variant="panel", visible=True):
2799
  streaming_chatbot_avatar_url = "https://storage.googleapis.com/wpassets.junyiacademy.org/1/2020/11/1-%E6%98%9F%E7%A9%BA%E9%A0%AD%E8%B2%BC-%E5%A4%AA%E7%A9%BA%E7%8B%90%E7%8B%B8%E8%B2%93-150x150.png"
 
2806
  chatbot_open_ai_streaming_name = gr.State("chatbot_open_ai_streaming")
2807
  gr.Image(value=streaming_chatbot_avatar_url, height=100, width=100, show_label=False, show_download_button=False)
2808
  chatbot_open_ai_streaming_select_btn = gr.Button("👆選擇【飛特音速】", elem_id="streaming_chatbot_btn", visible=True, variant="primary")
2809
+ with gr.Accordion("🚀 飛特音速 敘述", open=False):
2810
+ gr.Markdown(value=streaming_chatbot_description, visible=True)
2811
  # 尚未開放
2812
  with gr.Column(scale=1, variant="panel"):
2813
  gr.Markdown(value="### 尚未開放", visible=True)
 
3020
  questions_answers_create_button = gr.Button("重建", size="sm", variant="primary")
3021
  with gr.Row():
3022
  questions_answers_json = gr.Textbox(label="Questions Answers", lines=40, interactive=False, show_copy_button=True)
 
3023
  with gr.Tab("教學備課"):
3024
  with gr.Row() as worksheet_admin:
3025
  worksheet_kind = gr.Textbox(value="ai_content_list", show_label=False)
 
3029
  worksheet_delete_button = gr.Button("刪除", size="sm", variant="primary")
3030
  worksheet_create_button = gr.Button("重建(X)", size="sm", variant="primary", interactive=False)
3031
  with gr.Row():
3032
+ worksheet_json = gr.Textbox(label="worksheet", lines=40, interactive=False, show_copy_button=True)
 
3033
  with gr.Tab("逐字稿"):
3034
  simple_html_content = gr.HTML(label="Simple Transcript")
3035
  with gr.Tab("圖文"):
3036
  transcript_html = gr.HTML(label="YouTube Transcript and Video")
 
 
 
 
 
 
 
 
3037
  with gr.Tab("markdown"):
3038
  gr.Markdown("## 請複製以下 markdown 並貼到你的心智圖工具中,建議使用:https://markmap.js.org/repl")
3039
  mind_map = gr.Textbox(container=True, show_copy_button=True, lines=40, elem_id="mind_map_markdown")
 
3184
  mind_map_html,
3185
  transcript_html,
3186
  simple_html_content,
 
 
3187
  reading_passage_text,
3188
  reading_passage,
3189
  content_subject,