youngtsai commited on
Commit
31cd061
·
1 Parent(s): aad76cf
Files changed (1) hide show
  1. app.py +164 -145
app.py CHANGED
@@ -16,26 +16,55 @@ from initializer import initialize_clients, initialize_password
16
  GCS_SERVICE, GENAI_CLIENT = initialize_clients()
17
  GCS_CLIENT = GCS_SERVICE.client
18
 
 
19
  PASSWORD = initialize_password()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
20
 
 
21
  def toggle_visibility(toggle_value):
22
  return gr.update(visible=toggle_value)
23
 
24
- def mock_question_answer(question, history):
25
- # 假資料模擬回答
26
- answers = {
27
- "文件的核心觀點是什麼?": "這份文件的核心觀點是關於人工智慧如何提升工作效率。",
28
- "有哪些關鍵詞或數據?": "關鍵詞包括:人工智慧、工作效率、數據分析。",
29
- "文件的摘要是什麼?": "這份文件討論了如何利用人工智慧工具,提升企業的運營效率和決策速度。"
30
- }
31
- response = answers.get(question, "抱歉,我無法回答這個問題。請嘗試其他問題!")
32
- history.append({"role": "user", "content": question})
33
- history.append({"role": "assistant", "content": response})
34
- return history, ""
35
-
36
- def mock_summary():
37
- # 假資料模擬摘要
38
- return "這份文件主要討論人工智慧在工作效率提升方面的應用,並提供了實際案例來說明其價值。"
 
 
 
 
 
 
39
 
40
  def get_youtube_title_from_gemini(url):
41
  """使用 Gemini 獲取 YouTube 標題"""
@@ -108,6 +137,7 @@ def get_youtube_title(url):
108
  print(f"獲取標題失敗: {str(e)}")
109
  return url
110
 
 
111
  def add_to_file_list(file, file_list):
112
  if file:
113
  temp_dir = tempfile.gettempdir()
@@ -117,27 +147,96 @@ def add_to_file_list(file, file_list):
117
  display_list = [os.path.basename(path) if os.path.basename(path) else path for path in file_list]
118
  return gr.update(choices=display_list), None
119
 
120
- def add_youtube_to_list(youtube_link, file_list):
121
- if not youtube_link:
122
- return gr.update(choices=[item.split("|||")[0] if "|||" in item else os.path.basename(item) for item in file_list]), ""
123
-
124
- # 獲取標題
125
- title = get_youtube_title(youtube_link)
 
 
126
 
127
- # 確保 URL 格式完整
128
- if not youtube_link.startswith('http'):
129
- if 'watch?v=' in youtube_link:
130
- youtube_link = f'https://www.youtube.com/{youtube_link}'
131
- else:
132
- youtube_link = f'https://www.youtube.com/watch?v={youtube_link}'
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
133
 
134
- # 存儲格式:[title]|||[url]
135
- file_list.append(f"{title}|||{youtube_link}")
136
- display_list = [item.split("|||")[0] if "|||" in item else os.path.basename(item) for item in file_list]
137
- print(f"File list: {file_list}")
138
- print(f"Display list: {display_list}")
139
- return file_list, ""
 
 
 
 
 
 
 
 
140
 
 
 
141
  def generate_transcript(youtube_link):
142
  print(f"\n開始生成 YouTube 逐字稿: {youtube_link}")
143
  try:
@@ -198,25 +297,40 @@ def generate_summary(transcript):
198
  try:
199
  print("\n開始生成摘要...")
200
  model = "gemini-2.0-flash-exp"
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
201
  contents = [
202
  types.Content(
203
  role="user",
204
  parts=[
205
- types.Part.from_text(
206
- f"""請根據以下逐字稿生成重點摘要,以條列方式呈現主要觀點:
207
-
208
- {transcript}
209
-
210
- 請以下列格式輸出:
211
- # 主要觀點:
212
- 1. [重點1]
213
- 2. [重點2]
214
- ...
215
-
216
- # 結論:
217
- [整體結論]
218
- """
219
- )
220
  ]
221
  )
222
  ]
@@ -227,107 +341,12 @@ def generate_summary(transcript):
227
  )
228
 
229
  print("摘要生成完成!")
230
- return response.text
 
231
  except Exception as e:
232
  print(f"\n生成摘要時發生錯誤: {str(e)}")
233
  raise
234
 
235
- def process_all_files(file_list):
236
- """處理所有選中的文件"""
237
- if not file_list:
238
- return "請選擇要處理的文件", ""
239
-
240
- all_text = []
241
- status_messages = []
242
-
243
- for item in file_list:
244
- try:
245
- if "|||" in item:
246
- # YouTube 連結
247
- title, url = item.split("|||")
248
- print(f"處理 YouTube: {title}")
249
- try:
250
- transcript = generate_transcript(url)
251
- if transcript:
252
- all_text.append(f"=== {title} ===\n{transcript}")
253
- status_messages.append(f"🟢 成功處理 YouTube 影片:{title}")
254
- else:
255
- status_messages.append(f"🔴 無法獲取影片逐字稿:{title}")
256
- except Exception as e:
257
- if "無法取得影片資訊" in str(e):
258
- # 可能是影片標題問題,但還是有內容
259
- all_text.append(f"=== YouTube 影片 ===\n{e.transcript if hasattr(e, 'transcript') else ''}")
260
- status_messages.append(f"🟡 影片資訊不完整,但已處理內容:{url}")
261
- else:
262
- status_messages.append(f"🔴 處理失敗:{title}({str(e)})")
263
- else:
264
- # 本地文件
265
- filename = os.path.basename(item)
266
- print(f"處理文件: {filename}")
267
- try:
268
- with open(item, 'r', encoding='utf-8') as f:
269
- content = f.read()
270
- try:
271
- # 嘗試解碼文件名
272
- decoded_name = filename.encode('latin1').decode('utf-8')
273
- all_text.append(f"=== {decoded_name} ===\n{content}")
274
- status_messages.append(f"🟢 成功處理文件:{decoded_name}")
275
- except:
276
- # 文件名有問題,但內容可用
277
- all_text.append(f"=== 文件內容 ===\n{content}")
278
- status_messages.append(f"🟡 文件名稱無法正確顯示,但已處理內容:{filename}")
279
- except UnicodeDecodeError:
280
- try:
281
- # 嘗試其他編碼
282
- for encoding in ['big5', 'gbk', 'shift-jis']:
283
- try:
284
- with open(item, 'r', encoding=encoding) as f:
285
- content = f.read()
286
- all_text.append(f"=== {filename} ===\n{content}")
287
- status_messages.append(f"🟡 使用 {encoding} 編碼成功讀取文件:{filename}")
288
- break
289
- except:
290
- continue
291
- else:
292
- status_messages.append(f"🔴 無法讀取文件內容:{filename}(編碼問題)")
293
- except Exception as e:
294
- status_messages.append(f"🔴 讀取文件失敗:{filename}({str(e)})")
295
- except Exception as e:
296
- status_messages.append(f"🔴 讀取文件失敗:{filename}({str(e)})")
297
- except Exception as e:
298
- status_messages.append(f"🔴 處理失敗:{item}({str(e)})")
299
-
300
- if not all_text:
301
- return "❌ 沒有成功處理任何文件", ""
302
-
303
- # 合併所有文本
304
- combined_text = "\n\n".join(all_text)
305
- status_text = "\n".join(status_messages)
306
-
307
- return f"處理完成\n{status_text}", combined_text
308
-
309
- def process_with_auth(password, file_list, file_display):
310
- """帶密碼驗證的文件處理"""
311
- if not file_display: # 使用 file_display 而不是 file_list
312
- return "請選擇要處理的文件", "", gr.update(visible=False)
313
-
314
- if password != PASSWORD:
315
- return "���輸入正確的密碼", "", gr.update(visible=False)
316
-
317
- # 根據顯示的選項找到對應的完整項目
318
- selected_files = []
319
- for item in file_list:
320
- if "|||" in item:
321
- title = item.split("|||")[0]
322
- if title in file_display:
323
- selected_files.append(item)
324
- else:
325
- if os.path.basename(item) in file_display:
326
- selected_files.append(item)
327
-
328
- result_text, transcript_text = process_all_files(selected_files)
329
- return result_text, transcript_text, gr.update(visible=True)
330
-
331
  def on_summary_click(transcript):
332
  if not transcript:
333
  return "請先上傳文件或輸入 YouTube 連結並處理完成後再生成摘要。"
 
16
  GCS_SERVICE, GENAI_CLIENT = initialize_clients()
17
  GCS_CLIENT = GCS_SERVICE.client
18
 
19
+ # 密碼
20
  PASSWORD = initialize_password()
21
+ def process_with_auth(password, file_list, file_display):
22
+ """帶密碼驗證的文件處理"""
23
+ if not file_display: # 使用 file_display 而不是 file_list
24
+ return "請選擇要處理的文件", "", gr.update(visible=False)
25
+
26
+ if password != PASSWORD:
27
+ return "請輸入正確的密碼", "", gr.update(visible=False)
28
+
29
+ # 根據顯示的選項找到對應的完整項目
30
+ selected_files = []
31
+ for item in file_list:
32
+ if "|||" in item:
33
+ title = item.split("|||")[0]
34
+ if title in file_display:
35
+ selected_files.append(item)
36
+ else:
37
+ if os.path.basename(item) in file_display:
38
+ selected_files.append(item)
39
+
40
+ result_text, transcript_text = process_all_files(selected_files)
41
+ return result_text, transcript_text, gr.update(visible=True)
42
 
43
+ # UI 收合
44
  def toggle_visibility(toggle_value):
45
  return gr.update(visible=toggle_value)
46
 
47
+ # 來源 youtube
48
+ def add_youtube_to_list(youtube_link, file_list):
49
+ if not youtube_link:
50
+ return gr.update(choices=[item.split("|||")[0] if "|||" in item else os.path.basename(item) for item in file_list]), ""
51
+
52
+ # 獲取標題
53
+ title = get_youtube_title(youtube_link)
54
+
55
+ # 確保 URL 格式完整
56
+ if not youtube_link.startswith('http'):
57
+ if 'watch?v=' in youtube_link:
58
+ youtube_link = f'https://www.youtube.com/{youtube_link}'
59
+ else:
60
+ youtube_link = f'https://www.youtube.com/watch?v={youtube_link}'
61
+
62
+ # 存儲格式:[title]|||[url]
63
+ file_list.append(f"{title}|||{youtube_link}")
64
+ display_list = [item.split("|||")[0] if "|||" in item else os.path.basename(item) for item in file_list]
65
+ print(f"File list: {file_list}")
66
+ print(f"Display list: {display_list}")
67
+ return file_list, ""
68
 
69
  def get_youtube_title_from_gemini(url):
70
  """使用 Gemini 獲取 YouTube 標題"""
 
137
  print(f"獲取標題失敗: {str(e)}")
138
  return url
139
 
140
+ # 上傳檔案
141
  def add_to_file_list(file, file_list):
142
  if file:
143
  temp_dir = tempfile.gettempdir()
 
147
  display_list = [os.path.basename(path) if os.path.basename(path) else path for path in file_list]
148
  return gr.update(choices=display_list), None
149
 
150
+ # RAG 處理
151
+ def process_all_files(file_list):
152
+ """處理所有選中的文件"""
153
+ if not file_list:
154
+ return "請選擇要處理的文件", ""
155
+
156
+ all_text = []
157
+ status_messages = []
158
 
159
+ for item in file_list:
160
+ try:
161
+ if "|||" in item:
162
+ # YouTube 連結
163
+ title, url = item.split("|||")
164
+ print(f"處理 YouTube: {title}")
165
+ try:
166
+ transcript = generate_transcript(url)
167
+ if transcript:
168
+ all_text.append(f"=== {title} ===\n{transcript}")
169
+ status_messages.append(f"🟢 成功處理 YouTube 影片:{title}")
170
+ else:
171
+ status_messages.append(f"🔴 無法獲取影片逐字稿:{title}")
172
+ except Exception as e:
173
+ if "無法取得影片資訊" in str(e):
174
+ # 可能是影片標題問題,但還是有內容
175
+ all_text.append(f"=== YouTube 影片 ===\n{e.transcript if hasattr(e, 'transcript') else ''}")
176
+ status_messages.append(f"🟡 影片資訊不完整,但已處理內容:{url}")
177
+ else:
178
+ status_messages.append(f"🔴 處理失敗:{title}({str(e)})")
179
+ else:
180
+ # 本地文件
181
+ filename = os.path.basename(item)
182
+ print(f"處理文件: {filename}")
183
+ try:
184
+ with open(item, 'r', encoding='utf-8') as f:
185
+ content = f.read()
186
+ try:
187
+ # 嘗試解碼文件名
188
+ decoded_name = filename.encode('latin1').decode('utf-8')
189
+ all_text.append(f"=== {decoded_name} ===\n{content}")
190
+ status_messages.append(f"🟢 成功處理文件:{decoded_name}")
191
+ except:
192
+ # 文件名有問題,但內容可用
193
+ all_text.append(f"=== 文件內容 ===\n{content}")
194
+ status_messages.append(f"🟡 文件名稱無法正確顯示,但已處理內容:{filename}")
195
+ except UnicodeDecodeError:
196
+ try:
197
+ # 嘗試其他編碼
198
+ for encoding in ['big5', 'gbk', 'shift-jis']:
199
+ try:
200
+ with open(item, 'r', encoding=encoding) as f:
201
+ content = f.read()
202
+ all_text.append(f"=== {filename} ===\n{content}")
203
+ status_messages.append(f"🟡 使用 {encoding} 編碼成功讀取文件:{filename}")
204
+ break
205
+ except:
206
+ continue
207
+ else:
208
+ status_messages.append(f"🔴 無法讀取文件內容:{filename}(編碼問題)")
209
+ except Exception as e:
210
+ status_messages.append(f"🔴 讀取文件失敗:{filename}({str(e)})")
211
+ except Exception as e:
212
+ status_messages.append(f"🔴 讀取文件失敗:{filename}({str(e)})")
213
+ except Exception as e:
214
+ status_messages.append(f"🔴 處理失敗:{item}({str(e)})")
215
+
216
+ if not all_text:
217
+ return "❌ 沒有成功處理任何文件", ""
218
+
219
+ # 合併所有文本
220
+ combined_text = "\n\n".join(all_text)
221
+ status_text = "\n".join(status_messages)
222
 
223
+ return f"處理完成\n{status_text}", combined_text
224
+
225
+ # 對話
226
+ def mock_question_answer(question, history):
227
+ # 假資料模擬回答
228
+ answers = {
229
+ "文件的核心觀點是什麼?": "這份文件的核心觀點是關於人工智慧如何提升工作效率。",
230
+ "有哪些關鍵詞或數據?": "關鍵詞包括:人工智慧、工作效率、數據分析。",
231
+ "文件的摘要是什麼?": "這份文件討論了如何利用人工智慧工具,提升企業的運營效率和決策速度。"
232
+ }
233
+ response = answers.get(question, "抱歉,我無法回答這個問題。請嘗試其他問題!")
234
+ history.append({"role": "user", "content": question})
235
+ history.append({"role": "assistant", "content": response})
236
+ return history, ""
237
 
238
+
239
+ # 功能卡片
240
  def generate_transcript(youtube_link):
241
  print(f"\n開始生成 YouTube 逐字稿: {youtube_link}")
242
  try:
 
297
  try:
298
  print("\n開始生成摘要...")
299
  model = "gemini-2.0-flash-exp"
300
+ prompt = f"""
301
+ Inputs:
302
+ - 請根據以下逐字稿或文本生成重點摘要:{transcript}
303
+
304
+ Rules:
305
+ - 如果有課程名稱,請圍繞「課程名稱」為學習重點,進行重點整理,不要整理跟情境故事相關的問題
306
+ - 整體摘要在一百字以內
307
+ - 重點概念列出 bullet points,至少三個,最多五個
308
+ - 以及可能的結論與結尾延伸小問題提供學生作反思
309
+ - 敘述中,請把數學或是專業術語,用 Latex 包覆($...$)
310
+ - 加減乘除、根號、次方等等的運算式口語也換成 LATEX 數學符號
311
+
312
+ Example:
313
+ 請以下列 markdown 格式輸出:
314
+ ## 🌟 主題: (如果沒有 title 就省略)
315
+ ## 📚 整體摘要
316
+ - (一個 bullet point....)
317
+
318
+ ## 🔖 重點概念
319
+ - xxx
320
+ - xxx
321
+ - xxx
322
+
323
+ ## 💡 為什麼我們要學這個?
324
+ - (一個 bullet point....)
325
+
326
+ ## ❓ 延伸小問題
327
+ - (一個 bullet point....請圍繞學習重點,進行重點延伸思考,不要整理跟情境故事相關的問題)
328
+ """
329
  contents = [
330
  types.Content(
331
  role="user",
332
  parts=[
333
+ types.Part.from_text(prompt)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
334
  ]
335
  )
336
  ]
 
341
  )
342
 
343
  print("摘要生成完成!")
344
+ summary = response.text
345
+ return summary
346
  except Exception as e:
347
  print(f"\n生成摘要時發生錯誤: {str(e)}")
348
  raise
349
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
350
  def on_summary_click(transcript):
351
  if not transcript:
352
  return "請先上傳文件或輸入 YouTube 連結並處理完成後再生成摘要。"