3a05chatgpt commited on
Commit
6ae9e47
·
verified ·
1 Parent(s): 471dcf3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +56 -324
app.py CHANGED
@@ -1,6 +1,6 @@
1
  import openai
2
  import gradio as gr
3
- import fitz
4
  from openai import OpenAI
5
  import traceback
6
 
@@ -19,15 +19,16 @@ 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
  test_response = client.chat.completions.create(
29
- model="gpt-3.5-turbo", # 使用較便宜的模型測試
30
- messages=[{"role": "user", "content": "測試"}],
31
  max_tokens=5
32
  )
33
  return "✅ API Key 已設定並驗證成功!"
@@ -40,13 +41,11 @@ def set_api_key(user_api_key):
40
  return f"❌ API Key 設定失敗: {str(e)}"
41
 
42
  def set_model(model_name):
43
- """設定選擇的模型"""
44
  global selected_model
45
  selected_model = model_name
46
  return f"✅ 模型已選擇:{model_name}"
47
 
48
  def extract_pdf_text(file_path):
49
- """從 PDF 文件中提取文字"""
50
  try:
51
  doc = fitz.open(file_path)
52
  text = ""
@@ -60,371 +59,104 @@ def extract_pdf_text(file_path):
60
  return f"❌ PDF 解析錯誤: {str(e)}"
61
 
62
  def generate_summary(pdf_file):
63
- """從 PDF 內容生成摘要"""
64
  global summary_text, pdf_text
65
-
66
  if not client:
67
  return "❌ 請先設定 OpenAI API Key"
68
-
69
  if not pdf_file:
70
  return "❌ 請先上傳 PDF 文件"
71
-
72
  try:
73
- # 從 PDF 提取文字
74
  pdf_text = extract_pdf_text(pdf_file.name)
75
-
76
  if not pdf_text.strip():
77
  return "⚠️ 無法解析 PDF 文字,可能為純圖片 PDF 或空白文件。"
78
-
79
- # 截斷過長的文字
80
- max_chars = 8000
81
- if len(pdf_text) > max_chars:
82
- pdf_text_truncated = pdf_text[:max_chars] + "\n\n[文本已截斷,僅顯示前 8000 字符]"
83
- else:
84
- pdf_text_truncated = pdf_text
85
-
86
- # 生成摘要
87
  response = client.chat.completions.create(
88
  model=selected_model,
89
  messages=[
90
- {
91
- "role": "system",
92
- "content": """你是一個專業的文檔摘要助手。請將以下 PDF 內容整理為結構化的摘要:
93
-
94
- 1. 首先提供一個簡短的總體概述
95
- 2. 然後按照重要性列出主要重點(使用項目符號)
96
- 3. 如果有數據或統計信息,請特別標注
97
- 4. 如果有結論或建議,請單獨列出
98
-
99
- 請用繁體中文回答,保持專業且易於理解的語調。"""
100
- },
101
  {"role": "user", "content": pdf_text_truncated}
102
  ],
103
  temperature=0.3
104
  )
105
-
106
  summary_text = response.choices[0].message.content
107
  return summary_text
108
-
109
  except Exception as e:
110
- print(f"錯誤詳情: {traceback.format_exc()}")
111
  return f"❌ 摘要生成失敗: {str(e)}"
112
 
113
  def ask_question(user_question):
114
- """基於 PDF 內容回答問題"""
115
  if not client:
116
  return "❌ 請先設定 OpenAI API Key"
117
-
118
  if not summary_text and not pdf_text:
119
  return "❌ 請先生成 PDF 摘要"
120
-
121
  if not user_question.strip():
122
  return "❌ 請輸入問題"
123
-
124
  try:
125
- # 組合上下文
126
  context = f"PDF 摘要:\n{summary_text}\n\n原始內容(部分):\n{pdf_text[:2000]}"
127
-
128
  response = client.chat.completions.create(
129
  model=selected_model,
130
  messages=[
131
- {
132
- "role": "system",
133
- "content": f"""你是一個專業的文檔問答助手。請基於提供的 PDF 內容回答用戶問題。
134
-
135
- 規則:
136
- 1. 只根據提供的文檔內容回答
137
- 2. 如果文檔中沒有相關信息,請明確說明
138
- 3. 引用具體的文檔內容來支持你的回答
139
- 4. 用繁體中文回答
140
- 5. 保持客觀和準確
141
-
142
- 文檔內容:
143
- {context}"""
144
- },
145
  {"role": "user", "content": user_question}
146
  ],
147
  temperature=0.2
148
  )
149
-
150
  return response.choices[0].message.content
151
-
152
  except Exception as e:
153
- print(f"錯誤詳情: {traceback.format_exc()}")
154
  return f"❌ 問答生成失敗: {str(e)}"
155
 
156
  def clear_all():
157
- """清除所有資料"""
158
  global summary_text, pdf_text
159
  summary_text = ""
160
  pdf_text = ""
161
  return "", "", ""
162
 
163
- # 自定義 CSS 樣式 - 藍紫色主題
164
- custom_css = """
165
- /* 主要容器背景 */
166
- .gradio-container {
167
- background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
168
- min-height: 100vh;
169
- }
170
-
171
- /* 隱藏 Gradio footer 和 logo */
172
- footer { display: none !important; }
173
- .gradio-container footer { display: none !important; }
174
- div[class*="footer"] { display: none !important; }
175
- div[class*="Footer"] { display: none !important; }
176
- .gr-footer { display: none !important; }
177
-
178
- /* 標籤頁樣式 */
179
- .tab-nav {
180
- background: rgba(255, 255, 255, 0.1) !important;
181
- border-radius: 15px !important;
182
- backdrop-filter: blur(10px) !important;
183
- margin-bottom: 20px !important;
184
- }
185
-
186
- .tab-nav button {
187
- background: rgba(255, 255, 255, 0.1) !important;
188
- color: white !important;
189
- border: none !important;
190
- border-radius: 10px !important;
191
- margin: 5px !important;
192
- font-weight: 600 !important;
193
- transition: all 0.3s ease !important;
194
- }
195
-
196
- .tab-nav button:hover {
197
- background: rgba(255, 255, 255, 0.2) !important;
198
- transform: translateY(-2px) !important;
199
- }
200
-
201
- .tab-nav button.selected {
202
- background: rgba(255, 255, 255, 0.3) !important;
203
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.2) !important;
204
- }
205
-
206
- /* 卡片樣式 */
207
- .block {
208
- background: rgba(255, 255, 255, 0.95) !important;
209
- border-radius: 20px !important;
210
- box-shadow: 0 8px 32px rgba(0, 0, 0, 0.1) !important;
211
- backdrop-filter: blur(10px) !important;
212
- border: 1px solid rgba(255, 255, 255, 0.2) !important;
213
- margin: 10px 0 !important;
214
- padding: 20px !important;
215
- }
216
-
217
- /* 按鈕樣式 */
218
- .btn {
219
- background: linear-gradient(45deg, #667eea, #764ba2) !important;
220
- color: white !important;
221
- border: none !important;
222
- border-radius: 15px !important;
223
- padding: 12px 24px !important;
224
- font-weight: 600 !important;
225
- transition: all 0.3s ease !important;
226
- box-shadow: 0 4px 15px rgba(102, 126, 234, 0.4) !important;
227
- }
228
-
229
- .btn:hover {
230
- transform: translateY(-3px) !important;
231
- box-shadow: 0 6px 20px rgba(102, 126, 234, 0.6) !important;
232
- }
233
-
234
- .btn-secondary {
235
- background: linear-gradient(45deg, #a8a8a8, #6c757d) !important;
236
- box-shadow: 0 4px 15px rgba(168, 168, 168, 0.4) !important;
237
- }
238
-
239
- .btn-secondary:hover {
240
- box-shadow: 0 6px 20px rgba(168, 168, 168, 0.6) !important;
241
- }
242
-
243
- /* 輸入框樣式 */
244
- .gr-textbox, .gr-file, .gr-radio {
245
- border-radius: 15px !important;
246
- border: 2px solid rgba(102, 126, 234, 0.3) !important;
247
- background: rgba(255, 255, 255, 0.9) !important;
248
- transition: all 0.3s ease !important;
249
- }
250
-
251
- .gr-textbox:focus, .gr-file:focus {
252
- border-color: #667eea !important;
253
- box-shadow: 0 0 20px rgba(102, 126, 234, 0.3) !important;
254
- }
255
-
256
- /* 標題樣式 */
257
- h1, h2, h3 {
258
- color: white !important;
259
- text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.3) !important;
260
- font-weight: 700 !important;
261
- }
262
-
263
- /* Markdown 內容樣式 */
264
- .markdown {
265
- background: rgba(255, 255, 255, 0.95) !important;
266
- border-radius: 15px !important;
267
- padding: 20px !important;
268
- margin: 10px 0 !important;
269
- box-shadow: 0 4px 15px rgba(0, 0, 0, 0.1) !important;
270
- }
271
-
272
- /* 進度條樣式 */
273
- .progress {
274
- background: linear-gradient(45deg, #667eea, #764ba2) !important;
275
- border-radius: 10px !important;
276
- }
277
-
278
- /* 滾動條樣式 */
279
- ::-webkit-scrollbar {
280
- width: 8px;
281
- }
282
-
283
- ::-webkit-scrollbar-track {
284
- background: rgba(255, 255, 255, 0.1);
285
- border-radius: 10px;
286
- }
287
-
288
- ::-webkit-scrollbar-thumb {
289
- background: linear-gradient(45deg, #667eea, #764ba2);
290
- border-radius: 10px;
291
- }
292
-
293
- ::-webkit-scrollbar-thumb:hover {
294
- background: linear-gradient(45deg, #5a6fd8, #6a4190);
295
- }
296
-
297
- /* 動畫效果 */
298
- @keyframes fadeIn {
299
- from { opacity: 0; transform: translateY(20px); }
300
- to { opacity: 1; transform: translateY(0); }
301
- }
302
-
303
- .block {
304
- animation: fadeIn 0.6s ease-out !important;
305
- }
306
- """
307
-
308
- # 創建 Gradio 介面
309
  with gr.Blocks(
310
  title="PDF 摘要助手",
311
- css=custom_css,
312
- theme=gr.themes.Soft(
313
- primary_hue="blue",
314
- secondary_hue="purple",
315
- neutral_hue="slate",
316
- )
 
 
 
 
 
 
 
 
 
317
  ) as demo:
318
-
319
- gr.Markdown("""
320
- # 📄 PDF 摘要 & 問答助手
321
-
322
- 🚀 **歡迎使用 PDF 智能分析工具!**
323
-
324
- **主要功能:**
325
- - 📋 自動生成 PDF 文檔摘要
326
- - 🤖 基於文檔內容回答問題
327
- - 💡 快速理解長篇文檔的核心��容
328
-
329
- **使用步驟:**
330
- 1. 先在「設定」頁面輸入您的 OpenAI API Key
331
- 2. 選擇適合的 AI 模型
332
- 3. 在「摘要」頁面上傳 PDF 文件並生成摘要
333
- 4. 在「問答」頁面提出關於文件的問題
334
-
335
- ---
336
- """)
337
-
338
- with gr.Tab("🔧 設定"):
339
- gr.Markdown("### API Key 設定")
340
- api_key_input = gr.Textbox(
341
- label="🔑 輸入 OpenAI API Key",
342
- type="password",
343
- placeholder="請輸入您的 OpenAI API Key (sk-...)",
344
- elem_classes=["gr-textbox"]
345
- )
346
- api_key_btn = gr.Button("確認 API Key", variant="primary", elem_classes=["btn"])
347
- api_key_status = gr.Textbox(
348
- label="📊 API 狀態",
349
- interactive=False,
350
- value="🔄 等待設定 API Key...",
351
- elem_classes=["gr-textbox"]
352
- )
353
-
354
- gr.Markdown("### 模型選擇")
355
- model_choice = gr.Radio(
356
- ["gpt-4", "gpt-4.1", "gpt-4.5"],
357
- label="🤖 選擇 AI 模型",
358
- value="gpt-4",
359
- elem_classes=["gr-radio"]
360
- )
361
- model_status = gr.Textbox(
362
- label="🎯 模型狀態",
363
- interactive=False,
364
- value="✅ 已選擇:gpt-4",
365
- elem_classes=["gr-textbox"]
366
- )
367
-
368
- with gr.Tab("📄 PDF 摘要"):
369
- gr.Markdown("### 文件上傳與摘要生成")
370
- pdf_upload = gr.File(
371
- label="📁 上傳 PDF 文件",
372
- file_types=[".pdf"],
373
- elem_classes=["gr-file"]
374
- )
375
- with gr.Row():
376
- summary_btn = gr.Button("🔄 生成摘要", variant="primary", elem_classes=["btn"])
377
- clear_btn = gr.Button("🗑️ 清除資料", variant="secondary", elem_classes=["btn", "btn-secondary"])
378
-
379
- summary_output = gr.Textbox(
380
- label="📋 PDF 摘要",
381
- lines=15,
382
- placeholder="上傳 PDF 文件並點擊「生成摘要」按鈕,AI 將為您分析文檔內容...",
383
- elem_classes=["gr-textbox"]
384
- )
385
-
386
- with gr.Tab("❓ 智能問答"):
387
- gr.Markdown("### 基於文檔內容的問答")
388
- question_input = gr.Textbox(
389
- label="💭 請輸入您的問題",
390
- lines=3,
391
- placeholder="例如:這份文件的主要結論是什麼?文中提到的關鍵數據有哪些?",
392
- elem_classes=["gr-textbox"]
393
- )
394
- question_btn = gr.Button("📤 送出問題", variant="primary", elem_classes=["btn"])
395
-
396
- answer_output = gr.Textbox(
397
- label="🤖 AI 回答",
398
- lines=12,
399
- placeholder="請先上傳並生成 PDF 摘要,然後輸入問題,AI 將基於文檔內容為您提供回答...",
400
- elem_classes=["gr-textbox"]
401
- )
402
-
403
- gr.Markdown("""
404
- **💡 問題範例:**
405
- - 這份文件討論的主要議題是什麼?
406
- - 文中有哪些重要的統計數據?
407
- - 作者的主要觀點和結論是什麼?
408
- - 文件中提到的建議有哪些?
409
- """)
410
-
411
- # 事件綁定
412
- api_key_btn.click(set_api_key, inputs=api_key_input, outputs=api_key_status)
413
- api_key_input.submit(set_api_key, inputs=api_key_input, outputs=api_key_status)
414
-
415
- model_choice.change(set_model, inputs=model_choice, outputs=model_status)
416
-
417
- summary_btn.click(generate_summary, inputs=pdf_upload, outputs=summary_output)
418
-
419
- question_btn.click(ask_question, inputs=question_input, outputs=answer_output)
420
- question_input.submit(ask_question, inputs=question_input, outputs=answer_output)
421
-
422
- clear_btn.click(clear_all, outputs=[summary_output, question_input, answer_output])
423
 
424
  if __name__ == "__main__":
425
- demo.launch(
426
- show_error=True,
427
- share=True,
428
- server_name="0.0.0.0",
429
- server_port=7860
430
- )
 
1
  import openai
2
  import gradio as gr
3
+ import fitz # PyMuPDF
4
  from openai import OpenAI
5
  import traceback
6
 
 
19
  if not api_key:
20
  return "❌ API Key 不能為空"
21
 
22
+ # 支援新舊 key 格式
23
+ if not (api_key.startswith('sk-') or api_key.startswith('sk-proj-')):
24
+ return "❌ API Key 格式錯誤,必須以 'sk-' 或 'sk-proj-' 開頭"
25
+
26
  client = OpenAI(api_key=api_key)
27
 
28
  # 測試 API Key 是否有效
29
  test_response = client.chat.completions.create(
30
+ model="gpt-4",
31
+ messages=[{"role": "user", "content": "你好"}],
32
  max_tokens=5
33
  )
34
  return "✅ API Key 已設定並驗證成功!"
 
41
  return f"❌ API Key 設定失敗: {str(e)}"
42
 
43
  def set_model(model_name):
 
44
  global selected_model
45
  selected_model = model_name
46
  return f"✅ 模型已選擇:{model_name}"
47
 
48
  def extract_pdf_text(file_path):
 
49
  try:
50
  doc = fitz.open(file_path)
51
  text = ""
 
59
  return f"❌ PDF 解析錯誤: {str(e)}"
60
 
61
  def generate_summary(pdf_file):
 
62
  global summary_text, pdf_text
 
63
  if not client:
64
  return "❌ 請先設定 OpenAI API Key"
 
65
  if not pdf_file:
66
  return "❌ 請先上傳 PDF 文件"
 
67
  try:
 
68
  pdf_text = extract_pdf_text(pdf_file.name)
 
69
  if not pdf_text.strip():
70
  return "⚠️ 無法解析 PDF 文字,可能為純圖片 PDF 或空白文件。"
71
+ pdf_text_truncated = pdf_text[:8000]
 
 
 
 
 
 
 
 
72
  response = client.chat.completions.create(
73
  model=selected_model,
74
  messages=[
75
+ {"role": "system", "content": "請將以下 PDF 內容整理為條列式摘要,用繁體中文回答:"},
 
 
 
 
 
 
 
 
 
 
76
  {"role": "user", "content": pdf_text_truncated}
77
  ],
78
  temperature=0.3
79
  )
 
80
  summary_text = response.choices[0].message.content
81
  return summary_text
 
82
  except Exception as e:
83
+ print(traceback.format_exc())
84
  return f"❌ 摘要生成失敗: {str(e)}"
85
 
86
  def ask_question(user_question):
 
87
  if not client:
88
  return "❌ 請先設定 OpenAI API Key"
 
89
  if not summary_text and not pdf_text:
90
  return "❌ 請先生成 PDF 摘要"
 
91
  if not user_question.strip():
92
  return "❌ 請輸入問題"
 
93
  try:
 
94
  context = f"PDF 摘要:\n{summary_text}\n\n原始內容(部分):\n{pdf_text[:2000]}"
 
95
  response = client.chat.completions.create(
96
  model=selected_model,
97
  messages=[
98
+ {"role": "system", "content": f"根據以下 PDF 內容回答問題,請用繁體中文回答:\n{context}"},
 
 
 
 
 
 
 
 
 
 
 
 
 
99
  {"role": "user", "content": user_question}
100
  ],
101
  temperature=0.2
102
  )
 
103
  return response.choices[0].message.content
 
104
  except Exception as e:
105
+ print(traceback.format_exc())
106
  return f"❌ 問答生成失敗: {str(e)}"
107
 
108
  def clear_all():
 
109
  global summary_text, pdf_text
110
  summary_text = ""
111
  pdf_text = ""
112
  return "", "", ""
113
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
114
  with gr.Blocks(
115
  title="PDF 摘要助手",
116
+ css="""
117
+ .gradio-container {
118
+ max-width: none !important;
119
+ width: 100% !important;
120
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
121
+ min-height: 100vh;
122
+ }
123
+ .main-content {
124
+ max-width: 1600px !important;
125
+ margin: 20px auto !important;
126
+ padding: 30px !important;
127
+ background: rgba(255, 255, 255, 0.95) !important;
128
+ border-radius: 20px !important;
129
+ }
130
+ """
131
  ) as demo:
132
+ with gr.Column():
133
+ gr.Markdown("## 📄 PDF 摘要 & 問答助手")
134
+
135
+ with gr.Tab("🔧 設定"):
136
+ api_key_input = gr.Textbox(label="🔑 輸入 OpenAI API Key", type="password")
137
+ api_key_status = gr.Textbox(label="API 狀態", interactive=False, value="等待設定 API Key...")
138
+ api_key_btn = gr.Button("確認 API Key")
139
+ api_key_btn.click(set_api_key, inputs=api_key_input, outputs=api_key_status)
140
+
141
+ model_choice = gr.Radio(["gpt-4", "gpt-4.1", "gpt-4.5"], label="選擇 AI 模型", value="gpt-4")
142
+ model_status = gr.Textbox(label="模型狀態", interactive=False, value="✅ 已選擇:gpt-4")
143
+ model_choice.change(set_model, inputs=model_choice, outputs=model_status)
144
+
145
+ with gr.Tab("📄 摘要"):
146
+ pdf_upload = gr.File(label="上傳 PDF", file_types=[".pdf"])
147
+ summary_btn = gr.Button("生成摘要")
148
+ summary_output = gr.Textbox(label="PDF 摘要", lines=12)
149
+ summary_btn.click(generate_summary, inputs=pdf_upload, outputs=summary_output)
150
+
151
+ with gr.Tab("❓ 問答"):
152
+ question_input = gr.Textbox(label="請輸入問題", lines=2)
153
+ question_btn = gr.Button("送出問題")
154
+ answer_output = gr.Textbox(label="AI 回答", lines=8)
155
+ question_btn.click(ask_question, inputs=question_input, outputs=answer_output)
156
+ question_input.submit(ask_question, inputs=question_input, outputs=answer_output)
157
+
158
+ clear_btn = gr.Button("🗑️ 清除所有資料")
159
+ clear_btn.click(clear_all, outputs=[summary_output, question_input, answer_output])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
160
 
161
  if __name__ == "__main__":
162
+ demo.launch(show_error=True)