3a05chatgpt commited on
Commit
c0edcf5
·
verified ·
1 Parent(s): a3f064a

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +293 -127
app.py CHANGED
@@ -2,6 +2,8 @@ import openai
2
  import gradio as gr
3
  import fitz # PyMuPDF
4
  from openai import OpenAI
 
 
5
  import traceback
6
 
7
  # 全域變數
@@ -19,38 +21,24 @@ def set_api_key(user_api_key):
19
  if not api_key:
20
  return "❌ API Key 不能為空"
21
 
22
- if not api_key.startswith('sk-'):
23
- return "❌ API Key 格式錯誤,應該以 'sk-' 開頭"
24
-
25
  client = OpenAI(api_key=api_key)
26
 
27
  # 測試 API Key 是否有效
28
- try:
29
- test_response = client.chat.completions.create(
30
- model="gpt-3.5-turbo",
31
- messages=[{"role": "user", "content": "Hello"}],
32
- max_tokens=5
33
- )
34
- return "✅ API Key 已設定並驗證成功!可以開始使用了。"
35
- except Exception as e:
36
- if "incorrect_api_key" in str(e).lower():
37
- return "❌ API Key 無效,請檢查是否正確"
38
- elif "quota" in str(e).lower():
39
- return "⚠️ API Key 有效,但配額不足"
40
- else:
41
- return f"⚠️ API Key 設定成功,但測試時出現問題:{str(e)[:100]}"
42
-
43
  except Exception as e:
44
- return f"❌ 設定失敗:{str(e)}"
45
 
46
  def set_model(model_name):
47
  """設定選擇的模型"""
48
  global selected_model
49
- if model_name:
50
- selected_model = model_name
51
- return f"✅ 模型已更新為:{model_name}"
52
- else:
53
- return "❌ 請選擇一個模型"
54
 
55
  def extract_pdf_text(file_path):
56
  """從 PDF 文件中提取文字"""
@@ -59,7 +47,7 @@ def extract_pdf_text(file_path):
59
  text = ""
60
  for page_num, page in enumerate(doc):
61
  page_text = page.get_text()
62
- if page_text.strip():
63
  text += f"\n--- 第 {page_num + 1} 頁 ---\n"
64
  text += page_text
65
  doc.close()
@@ -78,17 +66,20 @@ def generate_summary(pdf_file):
78
  return "❌ 請先上傳 PDF 文件"
79
 
80
  try:
 
81
  pdf_text = extract_pdf_text(pdf_file.name)
82
 
83
  if not pdf_text.strip():
84
  return "⚠️ 無法解析 PDF 文字,可能為純圖片 PDF 或空白文件。"
85
 
86
- max_chars = 8000
 
87
  if len(pdf_text) > max_chars:
88
  pdf_text_truncated = pdf_text[:max_chars] + "\n\n[文本已截斷,僅顯示前 8000 字符]"
89
  else:
90
  pdf_text_truncated = pdf_text
91
 
 
92
  response = client.chat.completions.create(
93
  model=selected_model,
94
  messages=[
@@ -128,6 +119,7 @@ def ask_question(user_question):
128
  return "❌ 請輸入問題"
129
 
130
  try:
 
131
  context = f"PDF 摘要:\n{summary_text}\n\n原始內容(部分):\n{pdf_text[:2000]}"
132
 
133
  response = client.chat.completions.create(
@@ -166,127 +158,301 @@ def clear_all():
166
  pdf_text = ""
167
  return "", "", ""
168
 
169
- # 創建最簡單的 Gradio 介面
170
  with gr.Blocks(
 
171
  title="PDF 摘要助手",
172
  css="""
173
- /* 隱藏 Gradio logo 和 footer */
174
- footer { display: none !important; }
175
- .gradio-container footer { display: none !important; }
176
- div[class*="footer"] { display: none !important; }
177
- div[class*="Footer"] { display: none !important; }
178
- .gr-footer { display: none !important; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
- /* 強制修復按鈕 */
 
 
 
 
 
 
 
 
181
  button {
182
- pointer-events: auto !important;
183
- z-index: 9999 !important;
184
- position: relative !important;
 
 
 
 
 
 
185
  cursor: pointer !important;
 
 
186
  }
187
- """
188
- ) as demo:
189
- gr.Markdown("# 📄 PDF 摘要 & 問答助手")
190
 
191
- with gr.Tab("🔧 設定"):
192
- gr.Markdown("## API Key 設定")
193
-
194
- api_key_input = gr.Textbox(
195
- label="輸入 OpenAI API Key",
196
- type="password",
197
- placeholder="請輸入您的 OpenAI API Key (sk-...)"
198
- )
199
-
200
- with gr.Row():
201
- api_key_btn = gr.Button("確認設定 API Key", variant="primary")
202
-
203
- api_key_status = gr.Textbox(
204
- label="API 狀態",
205
- interactive=False,
206
- value="等待設定 API Key..."
207
- )
208
-
209
- gr.Markdown("## 模型選擇")
210
-
211
- model_choice = gr.Radio(
212
- choices=["gpt-4", "gpt-4.1", "gpt-4.5"],
213
- label="選擇 AI 模型",
214
- value="gpt-4"
215
- )
216
-
217
- with gr.Row():
218
- model_btn = gr.Button("確認選擇模型", variant="secondary")
219
-
220
- model_status = gr.Textbox(
221
- label="當前模型",
222
- interactive=False,
223
- value="已選擇:gpt-4"
224
- )
225
 
226
- with gr.Tab("📄 PDF 處理"):
227
- gr.Markdown("## 上傳 PDF 文件")
228
-
229
- pdf_upload = gr.File(
230
- label="選擇 PDF 文件",
231
- file_types=[".pdf"]
232
- )
233
-
234
- with gr.Row():
235
- summary_btn = gr.Button("生成摘要", variant="primary")
236
- clear_btn = gr.Button("清除資料", variant="secondary")
237
-
238
- summary_output = gr.Textbox(
239
- label="PDF 摘要",
240
- lines=15,
241
- placeholder="上傳 PDF 文件並點擊「生成摘要」按鈕"
242
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
243
 
244
- with gr.Tab("❓ 問答"):
245
- gr.Markdown("## AI 問答")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
246
 
247
- question_input = gr.Textbox(
248
- label="請輸入您的問題",
249
- placeholder="例如:這份文件的主要結論是什麼?",
250
- lines=3
251
- )
252
 
253
- with gr.Row():
254
- question_btn = gr.Button("發送問題", variant="primary")
 
255
 
256
- answer_output = gr.Textbox(
257
- label="AI 回答",
258
- lines=10,
259
- placeholder="AI 回答將顯示在這裡"
260
- )
261
-
262
- # 事件綁定 - 使用最明確的方式
263
- def handle_api_key():
264
- return set_api_key(api_key_input.value if hasattr(api_key_input, 'value') else "")
265
-
266
- def handle_model():
267
- return set_model(model_choice.value if hasattr(model_choice, 'value') else "gpt-4")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
268
 
269
- def handle_summary():
270
- return generate_summary(pdf_upload.value if hasattr(pdf_upload, 'value') else None)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
271
 
272
- def handle_question():
273
- return ask_question(question_input.value if hasattr(question_input, 'value') else "")
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
274
 
275
- # 綁定點擊事件
276
- api_key_btn.click(set_api_key, inputs=[api_key_input], outputs=[api_key_status])
277
- model_btn.click(set_model, inputs=[model_choice], outputs=[model_status])
278
- summary_btn.click(generate_summary, inputs=[pdf_upload], outputs=[summary_output])
279
- question_btn.click(ask_question, inputs=[question_input], outputs=[answer_output])
280
- clear_btn.click(clear_all, inputs=[], outputs=[summary_output, question_input, answer_output])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
 
282
- # Enter 鍵支援
283
- api_key_input.submit(set_api_key, inputs=[api_key_input], outputs=[api_key_status])
284
- question_input.submit(ask_question, inputs=[question_input], outputs=[answer_output])
 
 
 
 
285
 
286
  if __name__ == "__main__":
287
  demo.launch(
288
  share=False,
289
  show_api=False,
290
- show_error=True,
291
- favicon_path=None
292
  )
 
2
  import gradio as gr
3
  import fitz # PyMuPDF
4
  from openai import OpenAI
5
+ import os
6
+ import tempfile
7
  import traceback
8
 
9
  # 全域變數
 
21
  if not api_key:
22
  return "❌ API Key 不能為空"
23
 
 
 
 
24
  client = OpenAI(api_key=api_key)
25
 
26
  # 測試 API Key 是否有效
27
+ test_response = client.chat.completions.create(
28
+ model="gpt-4",
29
+ messages=[{"role": "user", "content": "你好"}],
30
+ max_tokens=5
31
+ )
32
+
33
+ return "✅ API Key 已設定並驗證成功"
 
 
 
 
 
 
 
 
34
  except Exception as e:
35
+ return f"❌ API Key 設定失敗: {str(e)}"
36
 
37
  def set_model(model_name):
38
  """設定選擇的模型"""
39
  global selected_model
40
+ selected_model = model_name
41
+ return f"✅ 模型已選擇:{model_name}"
 
 
 
42
 
43
  def extract_pdf_text(file_path):
44
  """從 PDF 文件中提取文字"""
 
47
  text = ""
48
  for page_num, page in enumerate(doc):
49
  page_text = page.get_text()
50
+ if page_text.strip(): # 只添加非空白頁面
51
  text += f"\n--- 第 {page_num + 1} 頁 ---\n"
52
  text += page_text
53
  doc.close()
 
66
  return "❌ 請先上傳 PDF 文件"
67
 
68
  try:
69
+ # 從 PDF 提取文字
70
  pdf_text = extract_pdf_text(pdf_file.name)
71
 
72
  if not pdf_text.strip():
73
  return "⚠️ 無法解析 PDF 文字,可能為純圖片 PDF 或空白文件。"
74
 
75
+ # 檢查文字長度,必要時截斷
76
+ max_chars = 8000 # 為系統提示留出空間
77
  if len(pdf_text) > max_chars:
78
  pdf_text_truncated = pdf_text[:max_chars] + "\n\n[文本已截斷,僅顯示前 8000 字符]"
79
  else:
80
  pdf_text_truncated = pdf_text
81
 
82
+ # 生成摘要
83
  response = client.chat.completions.create(
84
  model=selected_model,
85
  messages=[
 
119
  return "❌ 請輸入問題"
120
 
121
  try:
122
+ # 使用摘要和原始文本來提供更好的上下文
123
  context = f"PDF 摘要:\n{summary_text}\n\n原始內容(部分):\n{pdf_text[:2000]}"
124
 
125
  response = client.chat.completions.create(
 
158
  pdf_text = ""
159
  return "", "", ""
160
 
161
+ # 創建 Gradio 介面 - 全螢幕版本
162
  with gr.Blocks(
163
+ theme=gr.themes.Soft(),
164
  title="PDF 摘要助手",
165
  css="""
166
+ /* 全螢幕樣式 */
167
+ .gradio-container {
168
+ max-width: none !important;
169
+ width: 100vw !important;
170
+ height: 100vh !important;
171
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
172
+ margin: 0 !important;
173
+ padding: 0 !important;
174
+ }
175
+
176
+ /* 主要內容區域 - 全螢幕 */
177
+ .main-content {
178
+ background: rgba(255, 255, 255, 0.95) !important;
179
+ border-radius: 20px !important;
180
+ margin: 15px !important;
181
+ padding: 30px !important;
182
+ box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important;
183
+ backdrop-filter: blur(10px) !important;
184
+ width: calc(100vw - 30px) !important;
185
+ min-height: calc(100vh - 30px) !important;
186
+ box-sizing: border-box !important;
187
+ }
188
+
189
+ /* 標題樣式 */
190
+ .main-header {
191
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
192
+ -webkit-background-clip: text !important;
193
+ -webkit-text-fill-color: transparent !important;
194
+ text-align: center !important;
195
+ font-size: 2.8em !important;
196
+ font-weight: bold !important;
197
+ margin-bottom: 25px !important;
198
+ }
199
+
200
+ /* 分頁導航 */
201
+ .gradio-tabs {
202
+ border-radius: 15px !important;
203
+ overflow: hidden !important;
204
+ width: 100% !important;
205
+ }
206
+
207
+ .gradio-tabitem {
208
+ padding: 30px !important;
209
+ background: white !important;
210
+ border-radius: 0 0 15px 15px !important;
211
+ width: 100% !important;
212
+ }
213
+
214
+ /* 輸入框樣式 */
215
+ input[type="text"],
216
+ input[type="password"],
217
+ textarea {
218
+ border: 2px solid #e0e6ff !important;
219
+ border-radius: 12px !important;
220
+ padding: 15px !important;
221
+ font-size: 16px !important;
222
+ transition: all 0.3s ease !important;
223
+ width: 100% !important;
224
+ box-sizing: border-box !important;
225
+ }
226
 
227
+ input[type="text"]:focus,
228
+ input[type="password"]:focus,
229
+ textarea:focus {
230
+ border-color: #667eea !important;
231
+ box-shadow: 0 0 20px rgba(102, 126, 234, 0.3) !important;
232
+ outline: none !important;
233
+ }
234
+
235
+ /* 按鈕樣式 */
236
  button {
237
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
238
+ border: none !important;
239
+ border-radius: 12px !important;
240
+ color: white !important;
241
+ font-weight: 600 !important;
242
+ padding: 15px 30px !important;
243
+ font-size: 16px !important;
244
+ transition: all 0.3s ease !important;
245
+ box-shadow: 0 5px 15px rgba(102, 126, 234, 0.4) !important;
246
  cursor: pointer !important;
247
+ min-height: 50px !important;
248
+ margin: 8px 4px !important;
249
  }
 
 
 
250
 
251
+ button:hover {
252
+ transform: translateY(-2px) !important;
253
+ box-shadow: 0 8px 25px rgba(102, 126, 234, 0.6) !important;
254
+ }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
255
 
256
+ /* 次要按鈕 */
257
+ button[variant="secondary"] {
258
+ background: linear-gradient(135deg, #f093fb 0%, #f5576c 100%) !important;
259
+ }
260
+
261
+ button[variant="secondary"]:hover {
262
+ background: linear-gradient(135deg, #e081e9 0%, #e3455a 100%) !important;
263
+ }
264
+
265
+ /* 文件上傳區域 */
266
+ .file-upload {
267
+ border: 3px dashed #667eea !important;
268
+ border-radius: 15px !important;
269
+ background: rgba(102, 126, 234, 0.05) !important;
270
+ padding: 40px !important;
271
+ text-align: center !important;
272
+ transition: all 0.3s ease !important;
273
+ min-height: 120px !important;
274
+ width: 100% !important;
275
+ }
276
+
277
+ .file-upload:hover {
278
+ background: rgba(102, 126, 234, 0.1) !important;
279
+ border-color: #764ba2 !important;
280
+ }
281
+
282
+ /* 行和列的布局 */
283
+ .gr-row {
284
+ display: flex !important;
285
+ gap: 20px !important;
286
+ width: 100% !important;
287
+ margin: 15px 0 !important;
288
+ }
289
+
290
+ .gr-column {
291
+ flex: 1 !important;
292
+ min-width: 0 !important;
293
+ }
294
 
295
+ /* 文字輸出區域 */
296
+ .gr-textbox {
297
+ width: 100% !important;
298
+ }
299
+
300
+ /* 隱藏所有 footer 和 logo */
301
+ footer,
302
+ .gradio-container footer,
303
+ div[class*="footer"],
304
+ div[class*="Footer"],
305
+ .gr-footer,
306
+ .gradio-footer {
307
+ display: none !important;
308
+ }
309
+
310
+ /* 響應式設計 */
311
+ @media (max-width: 768px) {
312
+ .main-content {
313
+ margin: 5px !important;
314
+ padding: 15px !important;
315
+ width: calc(100vw - 10px) !important;
316
+ }
317
 
318
+ .main-header {
319
+ font-size: 2em !important;
320
+ }
 
 
321
 
322
+ .gr-row {
323
+ flex-direction: column !important;
324
+ }
325
 
326
+ button {
327
+ width: 100% !important;
328
+ margin: 5px 0 !important;
329
+ }
330
+ }
331
+ """
332
+ ) as demo:
333
+ with gr.Column(elem_classes="main-content"):
334
+ gr.HTML("""
335
+ <div class="main-header">📄 PDF 摘要 & 問答助手</div>
336
+
337
+ <div style="text-align: center; margin-bottom: 30px; padding: 25px; background: linear-gradient(135deg, rgba(102, 126, 234, 0.1) 0%, rgba(118, 75, 162, 0.1) 100%); border-radius: 15px; border-left: 5px solid #667eea;">
338
+ <h3 style="color: #667eea; margin-bottom: 15px;">🚀 歡迎使用 PDF 智能分析工具!</h3>
339
+ <div style="display: flex; justify-content: space-around; flex-wrap: wrap; margin: 20px 0;">
340
+ <div style="margin: 10px; padding: 15px; background: white; border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.1); min-width: 200px; flex: 1; max-width: 300px;">
341
+ <div style="font-size: 24px; margin-bottom: 10px;">📋</div>
342
+ <strong>智能摘要生成</strong><br>
343
+ <span style="color: #666;">自動分析 PDF 內容並生成結構化摘要</span>
344
+ </div>
345
+ <div style="margin: 10px; padding: 15px; background: white; border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.1); min-width: 200px; flex: 1; max-width: 300px;">
346
+ <div style="font-size: 24px; margin-bottom: 10px;">🤖</div>
347
+ <strong>AI 問答系統</strong><br>
348
+ <span style="color: #666;">基於文檔內容回答您的問題</span>
349
+ </div>
350
+ <div style="margin: 10px; padding: 15px; background: white; border-radius: 10px; box-shadow: 0 3px 10px rgba(0,0,0,0.1); min-width: 200px; flex: 1; max-width: 300px;">
351
+ <div style="font-size: 24px; margin-bottom: 10px;">💡</div>
352
+ <strong>快速理解</strong><br>
353
+ <span style="color: #666;">快速掌握長篇文檔的核心內容</span>
354
+ </div>
355
+ </div>
356
+ <div style="background: rgba(255, 193, 7, 0.1); padding: 15px; border-radius: 10px; border-left: 4px solid #ffc107; margin-top: 20px;">
357
+ <strong style="color: #e65100;">⚠️ 重要提醒:</strong> 使用前請先在「🔧 設定」頁面輸入您的 OpenAI API Key
358
+ </div>
359
+ </div>
360
+ """)
361
 
362
+ with gr.Tab("🔧 設定"):
363
+ gr.Markdown("### ⚙️ 基本設定")
364
+ with gr.Row():
365
+ with gr.Column():
366
+ api_key_input = gr.Textbox(
367
+ label="🔑 輸入 OpenAI API Key",
368
+ type="password",
369
+ placeholder="請輸入您的 OpenAI API Key (sk-...)"
370
+ )
371
+ api_key_status = gr.Textbox(
372
+ label="📊 API 連接狀態",
373
+ interactive=False,
374
+ value="🔄 等待設定 API Key..."
375
+ )
376
+
377
+ with gr.Column():
378
+ model_choice = gr.Radio(
379
+ ["gpt-4", "gpt-4.1", "gpt-4.5"],
380
+ label="🤖 選擇 AI 模型",
381
+ value="gpt-4"
382
+ )
383
+ model_status = gr.Textbox(
384
+ label="🎯 當前模型",
385
+ interactive=False,
386
+ value="✅ 已選擇:gpt-4"
387
+ )
388
 
389
+ with gr.Tab("📄 PDF 處理"):
390
+ gr.Markdown("### 📁 文件上傳與摘要生成")
391
+ with gr.Row():
392
+ with gr.Column():
393
+ pdf_upload = gr.File(
394
+ label="📎 選擇 PDF 文件",
395
+ file_types=[".pdf"]
396
+ )
397
+ gr.Markdown("**支援格式**:PDF 文件 (建議 < 10MB)")
398
+
399
+ with gr.Row():
400
+ summary_btn = gr.Button(
401
+ "🔄 開始生成摘要",
402
+ variant="primary"
403
+ )
404
+ clear_btn = gr.Button(
405
+ "🗑️ 清除所有資料",
406
+ variant="secondary"
407
+ )
408
+
409
+ with gr.Column():
410
+ summary_output = gr.Textbox(
411
+ label="📋 AI 生成的文檔摘要",
412
+ lines=20,
413
+ placeholder="📄 上傳 PDF 文件並點擊「開始生成摘要」按鈕,AI 將為您分析文檔內容..."
414
+ )
415
 
416
+ with gr.Tab("❓ 智能問答"):
417
+ gr.Markdown("### 💬 基於文檔內容的 AI 問答")
418
+ with gr.Row():
419
+ with gr.Column():
420
+ question_input = gr.Textbox(
421
+ label="💭 請輸入您的問題",
422
+ placeholder="例如:這份文件的主要結論是什麼?文中提到的關鍵數據有哪些?",
423
+ lines=4
424
+ )
425
+ question_btn = gr.Button(
426
+ "📤 發送問題",
427
+ variant="primary"
428
+ )
429
+
430
+ gr.Markdown("""
431
+ **💡 問題範例:**
432
+ - 這份文件討論的主要議題是什麼?
433
+ - 文中有哪些重要的統計數據?
434
+ - 作者的主要觀點和結論是什麼?
435
+ - 文件中提到的建議有哪些?
436
+ """)
437
+
438
+ with gr.Column():
439
+ answer_output = gr.Textbox(
440
+ label="🤖 AI 智能回答",
441
+ lines=18,
442
+ placeholder="🤖 請先上傳並生成 PDF 摘要,然後在左側輸入問題,AI 將基於文檔內容為您提供詳細回答..."
443
+ )
444
 
445
+ # 事件處理器
446
+ api_key_input.submit(set_api_key, inputs=api_key_input, outputs=api_key_status)
447
+ model_choice.change(set_model, inputs=model_choice, outputs=model_status)
448
+ summary_btn.click(generate_summary, inputs=pdf_upload, outputs=summary_output)
449
+ question_btn.click(ask_question, inputs=question_input, outputs=answer_output)
450
+ question_input.submit(ask_question, inputs=question_input, outputs=answer_output)
451
+ clear_btn.click(clear_all, outputs=[summary_output, question_input, answer_output])
452
 
453
  if __name__ == "__main__":
454
  demo.launch(
455
  share=False,
456
  show_api=False,
457
+ show_error=True
 
458
  )