DeepLearning101 commited on
Commit
3c568b2
·
verified ·
1 Parent(s): 9d4caaa

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +33 -21
app.py CHANGED
@@ -27,14 +27,11 @@ load_dotenv()
27
  class NotebookLMTool:
28
  def __init__(self):
29
  self.api_key = os.getenv("GEMINI_API_KEY")
30
- self.client = None
31
- if self.api_key:
32
- self.client = genai.Client(api_key=self.api_key)
33
 
34
  def set_key(self, user_key):
35
  if user_key and user_key.strip():
36
  self.api_key = user_key.strip()
37
- self.client = genai.Client(api_key=self.api_key)
38
  return "✅ API Key 已更新!"
39
  return "⚠️ Key 無效"
40
 
@@ -49,15 +46,21 @@ class NotebookLMTool:
49
  except:
50
  return []
51
 
52
- def _call_gemini_with_retry(self, model_name, contents, config=None, retries=5):
 
 
 
 
 
 
53
  """
54
- 封裝 Gemini 呼叫,加入指數退避重試機制 (Exponential Backoff)
55
  """
56
  delay = 5 # 初始等待秒數
57
 
58
  for attempt in range(retries):
59
  try:
60
- response = self.client.models.generate_content(
61
  model=model_name,
62
  contents=contents,
63
  config=config
@@ -65,14 +68,14 @@ class NotebookLMTool:
65
  return response
66
  except Exception as e:
67
  error_str = str(e)
68
- # 檢查是否為 Rate Limit 相關錯誤 (429, 503, Resource Exhausted)
69
  if "429" in error_str or "RESOURCE_EXHAUSTED" in error_str or "503" in error_str:
70
  wait_time = delay + random.uniform(0, 3)
71
  print(f"⚠️ API 忙碌 (Attempt {attempt+1}/{retries}),休息 {wait_time:.1f} 秒...", flush=True)
72
  time.sleep(wait_time)
73
- delay *= 1.5 # 遞增等待時間
74
  else:
75
- raise e # 其他錯誤 (如 400) 直接拋出
76
 
77
  raise Exception("API 重試多次失敗,請檢查配額。")
78
 
@@ -81,6 +84,13 @@ class NotebookLMTool:
81
  """處理單一頁面的:去字(背景) + 文字分析(Layout)"""
82
  print(f"🚀 [Page {page_index+1}] 啟動處理...", flush=True)
83
 
 
 
 
 
 
 
 
84
  result = {
85
  "index": page_index,
86
  "bg_path": None,
@@ -107,8 +117,9 @@ class NotebookLMTool:
107
  3. Output ONLY the image.
108
  """
109
 
110
- # 修正點 2: 使用 _call_gemini_with_retry 確保 429 時會重試
111
  resp_img = self._call_gemini_with_retry(
 
112
  model_name="gemini-2.5-flash-image",
113
  contents=[clean_prompt, img],
114
  config=types.GenerateContentConfig(response_modalities=["IMAGE"])
@@ -139,7 +150,7 @@ class NotebookLMTool:
139
  except Exception as e:
140
  print(f"❌ [Page {page_index+1}] Clean Error: {e}", flush=True)
141
 
142
- # 失敗回退原圖 (但標記為失敗)
143
  if not bg_success:
144
  img.save(final_bg_path)
145
  result["bg_path"] = final_bg_path
@@ -155,8 +166,9 @@ class NotebookLMTool:
155
  Each item: {"text": string, "box_2d": [ymin, xmin, ymax, xmax] (0-1000), "font_size": int, "color": hex, "is_bold": bool}
156
  """
157
 
158
- # 修正點 3: 使用 _call_gemini_with_retry
159
  resp_layout = self._call_gemini_with_retry(
 
160
  model_name="gemini-2.5-flash",
161
  contents=[layout_prompt, img],
162
  config=types.GenerateContentConfig(response_mime_type="application/json")
@@ -177,7 +189,7 @@ class NotebookLMTool:
177
  return result
178
 
179
  def process_pdf(self, pdf_file, progress=gr.Progress()):
180
- if not self.client:
181
  raise ValueError("請先輸入 Google API Key!")
182
 
183
  if pdf_file is None:
@@ -206,8 +218,7 @@ class NotebookLMTool:
206
  except Exception as e:
207
  raise ValueError(f"PDF 轉換失敗: {str(e)}")
208
 
209
- # 3. 平行處理 (Parallel Execution with Safety)
210
- # 降低併發數以適應免費版 API
211
  max_workers = 2
212
  results_map = {}
213
 
@@ -216,16 +227,17 @@ class NotebookLMTool:
216
  with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
217
  future_to_page = {}
218
  for i, img in enumerate(images):
219
- time.sleep(1.5) # 稍微加大間隔,避免同時撞牆
220
  future = executor.submit(self.process_single_page, i, img, img_output_dir)
221
  future_to_page[future] = i
222
 
223
  for future in concurrent.futures.as_completed(future_to_page):
224
  try:
225
  res = future.result()
226
- results_map[res["index"]] = res
227
- total_input_tokens += res["tokens_in"]
228
- total_output_tokens += res["tokens_out"]
 
229
  except Exception as exc:
230
  print(f"Page processing generated an exception: {exc}")
231
 
@@ -331,7 +343,7 @@ with gr.Blocks(title="NotebookLM Slide Restorer,PPT.404", theme=gr.themes.Soft
331
 
332
  gr.Markdown("---")
333
  pdf_input = gr.File(label="上傳 PDF")
334
- btn_process = gr.Button("🚀 開始還原 PPTX (穩定修復版)", variant="primary")
335
 
336
  with gr.Column():
337
  out_zip = gr.File(label="📦 下載完整包")
 
27
  class NotebookLMTool:
28
  def __init__(self):
29
  self.api_key = os.getenv("GEMINI_API_KEY")
30
+ # 移除全域 client,改為動態建立以確保執行緒安全
 
 
31
 
32
  def set_key(self, user_key):
33
  if user_key and user_key.strip():
34
  self.api_key = user_key.strip()
 
35
  return "✅ API Key 已更新!"
36
  return "⚠️ Key 無效"
37
 
 
46
  except:
47
  return []
48
 
49
+ def _create_client(self):
50
+ """為每個執行緒建立獨立的 Client"""
51
+ if not self.api_key:
52
+ raise ValueError("API Key 未設定")
53
+ return genai.Client(api_key=self.api_key)
54
+
55
+ def _call_gemini_with_retry(self, client, model_name, contents, config=None, retries=5):
56
  """
57
+ 封裝 Gemini 呼叫,加入指數退避重試機制
58
  """
59
  delay = 5 # 初始等待秒數
60
 
61
  for attempt in range(retries):
62
  try:
63
+ response = client.models.generate_content(
64
  model=model_name,
65
  contents=contents,
66
  config=config
 
68
  return response
69
  except Exception as e:
70
  error_str = str(e)
71
+ # 檢查是否為 Rate Limit 相關錯誤
72
  if "429" in error_str or "RESOURCE_EXHAUSTED" in error_str or "503" in error_str:
73
  wait_time = delay + random.uniform(0, 3)
74
  print(f"⚠️ API 忙碌 (Attempt {attempt+1}/{retries}),休息 {wait_time:.1f} 秒...", flush=True)
75
  time.sleep(wait_time)
76
+ delay *= 1.5
77
  else:
78
+ raise e
79
 
80
  raise Exception("API 重試多次失敗,請檢查配額。")
81
 
 
84
  """處理單一頁面的:去字(背景) + 文字分析(Layout)"""
85
  print(f"🚀 [Page {page_index+1}] 啟動處理...", flush=True)
86
 
87
+ # 關鍵修改:在此處建立獨立的 Client,避免執行緒衝突
88
+ try:
89
+ local_client = self._create_client()
90
+ except Exception as e:
91
+ print(f"❌ [Page {page_index+1}] Client Init Error: {e}")
92
+ return None
93
+
94
  result = {
95
  "index": page_index,
96
  "bg_path": None,
 
117
  3. Output ONLY the image.
118
  """
119
 
120
+ # 使用 gemini-2.5-flash-image (支援繪圖)
121
  resp_img = self._call_gemini_with_retry(
122
+ client=local_client,
123
  model_name="gemini-2.5-flash-image",
124
  contents=[clean_prompt, img],
125
  config=types.GenerateContentConfig(response_modalities=["IMAGE"])
 
150
  except Exception as e:
151
  print(f"❌ [Page {page_index+1}] Clean Error: {e}", flush=True)
152
 
153
+ # 失敗回退原圖
154
  if not bg_success:
155
  img.save(final_bg_path)
156
  result["bg_path"] = final_bg_path
 
166
  Each item: {"text": string, "box_2d": [ymin, xmin, ymax, xmax] (0-1000), "font_size": int, "color": hex, "is_bold": bool}
167
  """
168
 
169
+ # 使用一般的 flash 模型做文字分析
170
  resp_layout = self._call_gemini_with_retry(
171
+ client=local_client,
172
  model_name="gemini-2.5-flash",
173
  contents=[layout_prompt, img],
174
  config=types.GenerateContentConfig(response_mime_type="application/json")
 
189
  return result
190
 
191
  def process_pdf(self, pdf_file, progress=gr.Progress()):
192
+ if not self.api_key:
193
  raise ValueError("請先輸入 Google API Key!")
194
 
195
  if pdf_file is None:
 
218
  except Exception as e:
219
  raise ValueError(f"PDF 轉換失敗: {str(e)}")
220
 
221
+ # 3. 平行處理 (Parallel Execution)
 
222
  max_workers = 2
223
  results_map = {}
224
 
 
227
  with concurrent.futures.ThreadPoolExecutor(max_workers=max_workers) as executor:
228
  future_to_page = {}
229
  for i, img in enumerate(images):
230
+ time.sleep(1.5) # 錯開請求
231
  future = executor.submit(self.process_single_page, i, img, img_output_dir)
232
  future_to_page[future] = i
233
 
234
  for future in concurrent.futures.as_completed(future_to_page):
235
  try:
236
  res = future.result()
237
+ if res:
238
+ results_map[res["index"]] = res
239
+ total_input_tokens += res["tokens_in"]
240
+ total_output_tokens += res["tokens_out"]
241
  except Exception as exc:
242
  print(f"Page processing generated an exception: {exc}")
243
 
 
343
 
344
  gr.Markdown("---")
345
  pdf_input = gr.File(label="上傳 PDF")
346
+ btn_process = gr.Button("🚀 開始還原 PPTX (穩定版)", variant="primary")
347
 
348
  with gr.Column():
349
  out_zip = gr.File(label="📦 下載完整包")