3a05chatgpt commited on
Commit
364e767
·
verified ·
1 Parent(s): 0c8d1ca

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +216 -41
app.py CHANGED
@@ -2,8 +2,11 @@ import openai
2
  import gradio as gr
3
  import fitz # PyMuPDF
4
  from openai import OpenAI
 
 
5
  import traceback
6
 
 
7
  api_key = ""
8
  selected_model = "gpt-4"
9
  summary_text = ""
@@ -11,142 +14,314 @@ client = None
11
  pdf_text = ""
12
 
13
  def set_api_key(user_api_key):
 
14
  global api_key, client
15
  try:
16
  api_key = user_api_key.strip()
17
  if not api_key:
18
  return "❌ API Key 不能為空"
19
-
20
  client = OpenAI(api_key=api_key)
21
- client.chat.completions.create(
 
 
22
  model="gpt-4",
23
  messages=[{"role": "user", "content": "你好"}],
24
  max_tokens=5
25
  )
 
26
  return "✅ API Key 已設定並驗證成功"
27
  except Exception as e:
28
  return f"❌ API Key 設定失敗: {str(e)}"
29
 
30
  def set_model(model_name):
 
31
  global selected_model
32
  selected_model = model_name
33
  return f"✅ 模型已選擇:{model_name}"
34
 
35
  def extract_pdf_text(file_path):
 
36
  try:
37
  doc = fitz.open(file_path)
38
  text = ""
39
  for page_num, page in enumerate(doc):
40
  page_text = page.get_text()
41
- if page_text.strip():
42
- text += f"\n--- 第 {page_num + 1} 頁 ---\n{page_text}"
 
43
  doc.close()
44
  return text
45
  except Exception as e:
46
  return f"❌ PDF 解析錯誤: {str(e)}"
47
 
48
  def generate_summary(pdf_file):
 
49
  global summary_text, pdf_text
 
50
  if not client:
51
  return "❌ 請先設定 OpenAI API Key"
 
52
  if not pdf_file:
53
  return "❌ 請先上傳 PDF 文件"
 
54
  try:
 
55
  pdf_text = extract_pdf_text(pdf_file.name)
 
56
  if not pdf_text.strip():
57
  return "⚠️ 無法解析 PDF 文字,可能為純圖片 PDF 或空白文件。"
58
- pdf_text_truncated = pdf_text[:8000]
 
 
 
 
 
 
 
 
59
  response = client.chat.completions.create(
60
  model=selected_model,
61
  messages=[
62
- {"role": "system", "content": "請將以下 PDF 內容整理為條列式摘要,用繁體中文回答:"},
 
 
 
 
 
 
 
 
 
 
63
  {"role": "user", "content": pdf_text_truncated}
64
  ],
65
  temperature=0.3
66
  )
 
67
  summary_text = response.choices[0].message.content
68
  return summary_text
 
69
  except Exception as e:
70
- print(traceback.format_exc())
71
- return f" 摘要生成失敗: {str(e)}"
 
72
 
73
  def ask_question(user_question):
 
74
  if not client:
75
  return "❌ 請先設定 OpenAI API Key"
 
76
  if not summary_text and not pdf_text:
77
  return "❌ 請先生成 PDF 摘要"
 
78
  if not user_question.strip():
79
  return "❌ 請輸入問題"
 
80
  try:
 
81
  context = f"PDF 摘要:\n{summary_text}\n\n原始內容(部分):\n{pdf_text[:2000]}"
 
82
  response = client.chat.completions.create(
83
  model=selected_model,
84
  messages=[
85
- {"role": "system", "content": f"根據以下 PDF 內容回答問題,請用繁體中文回答:\n{context}"},
 
 
 
 
 
 
 
 
 
 
 
 
 
86
  {"role": "user", "content": user_question}
87
  ],
88
  temperature=0.2
89
  )
 
90
  return response.choices[0].message.content
 
91
  except Exception as e:
92
- print(traceback.format_exc())
93
- return f" 問答生成失敗: {str(e)}"
 
94
 
95
  def clear_all():
 
96
  global summary_text, pdf_text
97
  summary_text = ""
98
  pdf_text = ""
99
  return "", "", ""
100
 
 
101
  with gr.Blocks(
102
  theme=gr.themes.Soft(),
103
  title="PDF 摘要助手",
104
  css="""
 
105
  .gradio-container {
106
  max-width: none !important;
107
- width: 100% !important;
 
108
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%) !important;
109
- min-height: 100vh;
 
110
  }
 
 
111
  .main-content {
112
  background: rgba(255, 255, 255, 0.95) !important;
113
  border-radius: 20px !important;
114
- margin: 20px auto !important;
115
  padding: 30px !important;
116
- max-width: 1600px !important;
117
  box-shadow: 0 20px 40px rgba(0, 0, 0, 0.1) !important;
118
  backdrop-filter: blur(10px) !important;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
119
  }
120
  """
121
  ) as demo:
122
  with gr.Column(elem_classes="main-content"):
123
- gr.Markdown("# 📄 PDF 摘要 & 問答助手")
124
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
125
  with gr.Tab("🔧 設定"):
126
- api_key_input = gr.Textbox(label="🔑 ���入 OpenAI API Key", type="password")
127
- api_key_status = gr.Textbox(label="狀態", interactive=False, value="等待設定 API Key...")
128
- api_key_btn = gr.Button("確認 API Key")
129
- api_key_btn.click(set_api_key, inputs=api_key_input, outputs=api_key_status)
130
-
131
- model_choice = gr.Radio(["gpt-4", "gpt-4.1", "gpt-4.5"], label="選擇 AI 模型", value="gpt-4")
132
- model_status = gr.Textbox(label="模型狀態", interactive=False, value="✅ 已選擇:gpt-4")
133
- model_choice.change(set_model, inputs=model_choice, outputs=model_status)
134
-
135
- with gr.Tab("📄 摘要"):
136
- pdf_upload = gr.File(label="上傳 PDF", file_types=[".pdf"])
137
- summary_btn = gr.Button("生成摘要")
138
- summary_output = gr.Textbox(label="PDF 摘要", lines=12)
139
- summary_btn.click(generate_summary, inputs=pdf_upload, outputs=summary_output)
140
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  with gr.Tab("❓ 問答"):
142
- question_input = gr.Textbox(label="請輸入問題", lines=2)
143
- question_btn = gr.Button("送出問題")
144
- answer_output = gr.Textbox(label="AI 回答", lines=8)
145
- question_btn.click(ask_question, inputs=question_input, outputs=answer_output)
146
- question_input.submit(ask_question, inputs=question_input, outputs=answer_output)
147
-
148
- clear_btn = gr.Button("🗑️ 清除所有資料")
149
- clear_btn.click(clear_all, outputs=[summary_output, question_input, answer_output])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
150
 
151
  if __name__ == "__main__":
152
- demo.launch(show_error=True)
 
 
 
 
 
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
+ # 全域變數
10
  api_key = ""
11
  selected_model = "gpt-4"
12
  summary_text = ""
 
14
  pdf_text = ""
15
 
16
  def set_api_key(user_api_key):
17
+ """設定 OpenAI API Key 並初始化客戶端"""
18
  global api_key, client
19
  try:
20
  api_key = user_api_key.strip()
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 文件中提取文字"""
45
  try:
46
  doc = fitz.open(file_path)
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()
54
  return text
55
  except Exception as e:
56
  return f"❌ PDF 解析錯誤: {str(e)}"
57
 
58
  def generate_summary(pdf_file):
59
+ """從 PDF 內容生成摘要"""
60
  global summary_text, pdf_text
61
+
62
  if not client:
63
  return "❌ 請先設定 OpenAI API Key"
64
+
65
  if not pdf_file:
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=[
86
+ {
87
+ "role": "system",
88
+ "content": """你是一個專業的文檔摘要助手。請將以下 PDF 內容整理為結構化的摘要:
89
+
90
+ 1. 首先提供一個簡短的總體概述
91
+ 2. 然後按照重要性列出主要重點(使用項目符號)
92
+ 3. 如果有數據或統計信息,請特別標注
93
+ 4. 如果有結論或建議,請單獨列出
94
+
95
+ 請用繁體中文回答,保持專業且易於理解的語調。"""
96
+ },
97
  {"role": "user", "content": pdf_text_truncated}
98
  ],
99
  temperature=0.3
100
  )
101
+
102
  summary_text = response.choices[0].message.content
103
  return summary_text
104
+
105
  except Exception as e:
106
+ error_msg = f"❌ 摘要生成失敗: {str(e)}"
107
+ print(f"錯誤詳情: {traceback.format_exc()}")
108
+ return error_msg
109
 
110
  def ask_question(user_question):
111
+ """基於 PDF 內容回答問題"""
112
  if not client:
113
  return "❌ 請先設定 OpenAI API Key"
114
+
115
  if not summary_text and not pdf_text:
116
  return "❌ 請先生成 PDF 摘要"
117
+
118
  if not user_question.strip():
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(
126
  model=selected_model,
127
  messages=[
128
+ {
129
+ "role": "system",
130
+ "content": f"""你是一個專業的文檔問答助手。請基於提供的 PDF 內容回答用戶問題。
131
+
132
+ 規則:
133
+ 1. 只根據提供的文檔內容回答
134
+ 2. 如果文檔中沒有相關信息,請明確說明
135
+ 3. 引用具體的文檔內容來支持你的回答
136
+ 4. 用繁體中文回答
137
+ 5. 保持客觀和準確
138
+
139
+ 文檔內容:
140
+ {context}"""
141
+ },
142
  {"role": "user", "content": user_question}
143
  ],
144
  temperature=0.2
145
  )
146
+
147
  return response.choices[0].message.content
148
+
149
  except Exception as e:
150
+ error_msg = f"❌ 問答生成失敗: {str(e)}"
151
+ print(f"錯誤詳情: {traceback.format_exc()}")
152
+ return error_msg
153
 
154
  def clear_all():
155
+ """清除所有資料"""
156
  global summary_text, pdf_text
157
  summary_text = ""
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
+ /* 隱藏所有 footer 和 logo */
201
+ footer,
202
+ .gradio-container footer,
203
+ div[class*="footer"],
204
+ div[class*="Footer"],
205
+ .gr-footer,
206
+ .gradio-footer {
207
+ display: none !important;
208
+ }
209
+
210
+ /* 響應式設計 */
211
+ @media (max-width: 768px) {
212
+ .main-content {
213
+ margin: 5px !important;
214
+ padding: 15px !important;
215
+ width: calc(100vw - 10px) !important;
216
+ }
217
+
218
+ .main-header {
219
+ font-size: 2em !important;
220
+ }
221
  }
222
  """
223
  ) as demo:
224
  with gr.Column(elem_classes="main-content"):
225
+ gr.HTML("""
226
+ <div class="main-header">📄 PDF 摘要 & 問答助手</div>
227
+
228
+ <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;">
229
+ <h3 style="color: #667eea; margin-bottom: 15px;">🚀 歡迎使用 PDF 智能分析工具!</h3>
230
+ <div style="display: flex; justify-content: space-around; flex-wrap: wrap; margin: 20px 0;">
231
+ <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;">
232
+ <div style="font-size: 24px; margin-bottom: 10px;">📋</div>
233
+ <strong>智能摘要生成</strong><br>
234
+ <span style="color: #666;">自動分析 PDF 內容並生成結構化摘要</span>
235
+ </div>
236
+ <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;">
237
+ <div style="font-size: 24px; margin-bottom: 10px;">🤖</div>
238
+ <strong>AI 問答系統</strong><br>
239
+ <span style="color: #666;">基於文檔內容回答您的問題</span>
240
+ </div>
241
+ <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;">
242
+ <div style="font-size: 24px; margin-bottom: 10px;">💡</div>
243
+ <strong>快速理解</strong><br>
244
+ <span style="color: #666;">快速掌握長篇文檔的核心內容</span>
245
+ </div>
246
+ </div>
247
+ <div style="background: rgba(255, 193, 7, 0.1); padding: 15px; border-radius: 10px; border-left: 4px solid #ffc107; margin-top: 20px;">
248
+ <strong style="color: #e65100;">⚠️ 重要提醒:</strong> 使用前請先在「🔧 設定」頁面輸入您的 OpenAI API Key
249
+ </div>
250
+ </div>
251
+ """)
252
+
253
  with gr.Tab("🔧 設定"):
254
+ with gr.Row():
255
+ with gr.Column():
256
+ api_key_input = gr.Textbox(
257
+ label="🔑 輸入 OpenAI API Key",
258
+ type="password",
259
+ placeholder="請輸入您的 OpenAI API Key"
260
+ )
261
+ api_key_status = gr.Textbox(
262
+ label="API 狀態",
263
+ interactive=False,
264
+ value="等待設定 API Key..."
265
+ )
266
+
267
+ with gr.Column():
268
+ model_choice = gr.Radio(
269
+ ["gpt-4", "gpt-4.1", "gpt-4.5"],
270
+ label="🤖 選擇模型",
271
+ value="gpt-4"
272
+ )
273
+ model_status = gr.Textbox(
274
+ label="模型狀態",
275
+ interactive=False,
276
+ value="✅ 模型已選擇:gpt-4"
277
+ )
278
+
279
+ with gr.Tab("📄 PDF 處理"):
280
+ with gr.Row():
281
+ with gr.Column():
282
+ pdf_upload = gr.File(
283
+ label="📁 上傳 PDF 文件",
284
+ file_types=[".pdf"]
285
+ )
286
+ with gr.Row():
287
+ summary_btn = gr.Button("🔄 生成摘要", variant="primary")
288
+ clear_btn = gr.Button("🗑️ 清除資料", variant="secondary")
289
+
290
+ with gr.Column():
291
+ summary_output = gr.Textbox(
292
+ label="📋 PDF 摘要",
293
+ lines=20,
294
+ placeholder="上傳 PDF 文件並點擊 '生成摘要' 按鈕"
295
+ )
296
+
297
  with gr.Tab("❓ 問答"):
298
+ with gr.Row():
299
+ with gr.Column():
300
+ question_input = gr.Textbox(
301
+ label="💬 請輸入您的問題",
302
+ placeholder="例如:這份文件的主要結論是什麼?",
303
+ lines=4
304
+ )
305
+ question_btn = gr.Button("📤 送出問題", variant="primary")
306
+
307
+ with gr.Column():
308
+ answer_output = gr.Textbox(
309
+ label="🤖 AI 回答",
310
+ lines=18,
311
+ placeholder="AI 回答將顯示在這裡"
312
+ )
313
+
314
+ # 事件處理器 - 使用舊版的簡單方式
315
+ api_key_input.submit(set_api_key, inputs=api_key_input, outputs=api_key_status)
316
+ model_choice.change(set_model, inputs=model_choice, outputs=model_status)
317
+ summary_btn.click(generate_summary, inputs=pdf_upload, outputs=summary_output)
318
+ question_btn.click(ask_question, inputs=question_input, outputs=answer_output)
319
+ question_input.submit(ask_question, inputs=question_input, outputs=answer_output)
320
+ clear_btn.click(clear_all, outputs=[summary_output, question_input, answer_output])
321
 
322
  if __name__ == "__main__":
323
+ demo.launch(
324
+ share=False,
325
+ show_api=False,
326
+ show_error=True
327
+ )