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

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +209 -49
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
 
@@ -12,121 +12,281 @@ client = None
12
  pdf_text = ""
13
 
14
  def set_api_key(user_api_key):
 
15
  global api_key, client
16
  try:
17
  api_key = user_api_key.strip()
18
  if not api_key:
19
  return "❌ API Key 不能為空"
20
-
 
 
 
21
  client = OpenAI(api_key=api_key)
22
- client.chat.completions.create(
23
- model="gpt-4",
24
- messages=[{"role": "user", "content": "你好"}],
25
- max_tokens=5
26
- )
27
- return " API Key 已設定並驗證成功"
 
 
 
 
 
 
 
 
 
 
 
28
  except Exception as e:
29
- return f"❌ API Key 設定失敗: {str(e)}"
30
 
31
  def set_model(model_name):
 
32
  global selected_model
33
- selected_model = model_name
34
- return f"✅ 模型已選擇:{model_name}"
 
 
 
35
 
36
  def extract_pdf_text(file_path):
 
37
  try:
38
  doc = fitz.open(file_path)
39
  text = ""
40
  for page_num, page in enumerate(doc):
41
  page_text = page.get_text()
42
  if page_text.strip():
43
- text += f"\n--- 第 {page_num + 1} 頁 ---\n{page_text}"
 
44
  doc.close()
45
  return text
46
  except Exception as e:
47
  return f"❌ PDF 解析錯誤: {str(e)}"
48
 
49
  def generate_summary(pdf_file):
 
50
  global summary_text, pdf_text
 
51
  if not client:
52
- return "❌ 請先設定 API Key"
 
53
  if not pdf_file:
54
  return "❌ 請先上傳 PDF 文件"
 
55
  try:
56
  pdf_text = extract_pdf_text(pdf_file.name)
 
57
  if not pdf_text.strip():
58
- return "⚠️ 無法解析 PDF 文字"
59
- pdf_text_truncated = pdf_text[:8000]
 
 
 
 
 
 
60
  response = client.chat.completions.create(
61
  model=selected_model,
62
  messages=[
63
- {"role": "system", "content": "請用繁體中文整理以下 PDF 內容摘要。"},
 
 
 
 
 
 
 
 
 
 
64
  {"role": "user", "content": pdf_text_truncated}
65
  ],
66
  temperature=0.3
67
  )
 
68
  summary_text = response.choices[0].message.content
69
  return summary_text
 
70
  except Exception as e:
71
- print(traceback.format_exc())
72
- return f" 摘要生成失敗: {str(e)}"
 
73
 
74
  def ask_question(user_question):
 
75
  if not client:
76
- return "❌ 請先設定 API Key"
 
77
  if not summary_text and not pdf_text:
78
  return "❌ 請先生成 PDF 摘要"
 
79
  if not user_question.strip():
80
  return "❌ 請輸入問題"
 
81
  try:
82
  context = f"PDF 摘要:\n{summary_text}\n\n原始���容(部分):\n{pdf_text[:2000]}"
 
83
  response = client.chat.completions.create(
84
  model=selected_model,
85
  messages=[
86
- {"role": "system", "content": f"根據以下 PDF 內容回答問題,用繁體中文:\n{context}"},
 
 
 
 
 
 
 
 
 
 
 
 
 
87
  {"role": "user", "content": user_question}
88
  ],
89
  temperature=0.2
90
  )
 
91
  return response.choices[0].message.content
 
92
  except Exception as e:
93
- print(traceback.format_exc())
94
- return f" 問答生成失敗: {str(e)}"
 
95
 
96
  def clear_all():
 
97
  global summary_text, pdf_text
98
  summary_text = ""
99
  pdf_text = ""
100
  return "", "", ""
101
 
102
- with gr.Blocks(title="PDF 摘要助手") as demo:
103
- gr.Markdown("## 📄 PDF 摘要 & 問答助手")
104
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
105
  with gr.Tab("🔧 設定"):
106
- api_key_input = gr.Textbox(label="輸入 OpenAI API Key", type="password")
107
- api_key_status = gr.Textbox(label="API 狀態", interactive=False)
108
- api_key_btn = gr.Button("確認 API Key")
109
- api_key_btn.click(set_api_key, inputs=api_key_input, outputs=api_key_status)
110
-
111
- model_choice = gr.Radio(["gpt-4", "gpt-4.1", "gpt-4.5"], label="選擇 AI 模型", value="gpt-4")
112
- model_status = gr.Textbox(label="模型狀態", interactive=False, value="✅ 已選擇:gpt-4")
113
- model_choice.change(set_model, inputs=model_choice, outputs=model_status)
114
-
115
- with gr.Tab("📄 摘要"):
116
- pdf_upload = gr.File(label="上傳 PDF", file_types=[".pdf"])
117
- summary_btn = gr.Button("生成摘要")
118
- summary_output = gr.Textbox(label="PDF 摘要", lines=10)
119
- summary_btn.click(generate_summary, inputs=pdf_upload, outputs=summary_output)
120
-
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
121
  with gr.Tab("❓ 問答"):
122
- question_input = gr.Textbox(label="請輸入問題", lines=2)
123
- question_btn = gr.Button("送出問題")
124
- answer_output = gr.Textbox(label="AI 回答", lines=8)
125
- question_btn.click(ask_question, inputs=question_input, outputs=answer_output)
126
- question_input.submit(ask_question, inputs=question_input, outputs=answer_output)
127
-
128
- clear_btn = gr.Button("清除所有資料")
129
- clear_btn.click(clear_all, outputs=[summary_output, question_input, answer_output])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
130
 
131
  if __name__ == "__main__":
132
- demo.launch(show_error=True)
 
 
 
 
 
 
1
  import openai
2
  import gradio as gr
3
+ import fitz # PyMuPDF
4
  from openai import OpenAI
5
  import traceback
6
 
 
12
  pdf_text = ""
13
 
14
  def set_api_key(user_api_key):
15
+ """設定 OpenAI API Key 並初始化客戶端"""
16
  global api_key, client
17
  try:
18
  api_key = user_api_key.strip()
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 文件中提取文字"""
57
  try:
58
  doc = fitz.open(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()
66
  return text
67
  except Exception as e:
68
  return f"❌ PDF 解析錯誤: {str(e)}"
69
 
70
  def generate_summary(pdf_file):
71
+ """從 PDF 內容生成摘要"""
72
  global summary_text, pdf_text
73
+
74
  if not client:
75
+ return "❌ 請先設定 OpenAI API Key"
76
+
77
  if not 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=[
95
+ {
96
+ "role": "system",
97
+ "content": """你是一個專業的文檔摘要助手。請將以下 PDF 內容整理為結構化的摘要:
98
+
99
+ 1. 首先提供一個簡短的總體概述
100
+ 2. 然後按照重要性列出主要重點(使用項目符號)
101
+ 3. 如果有數據或統計信息,請特別標注
102
+ 4. 如果有結論或建議,請單獨列出
103
+
104
+ 請用繁體中文回答,保持專業且易於理解的語調。"""
105
+ },
106
  {"role": "user", "content": pdf_text_truncated}
107
  ],
108
  temperature=0.3
109
  )
110
+
111
  summary_text = response.choices[0].message.content
112
  return summary_text
113
+
114
  except Exception as e:
115
+ error_msg = f"❌ 摘要生成失敗: {str(e)}"
116
+ print(f"錯誤詳情: {traceback.format_exc()}")
117
+ return error_msg
118
 
119
  def ask_question(user_question):
120
+ """基於 PDF 內容回答問題"""
121
  if not client:
122
+ return "❌ 請先設定 OpenAI API Key"
123
+
124
  if not summary_text and not pdf_text:
125
  return "❌ 請先生成 PDF 摘要"
126
+
127
  if not user_question.strip():
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(
134
  model=selected_model,
135
  messages=[
136
+ {
137
+ "role": "system",
138
+ "content": f"""你是一個專業的文檔問答助手。請基於提供的 PDF 內容回答用戶問題。
139
+
140
+ 規則:
141
+ 1. 只根據提供的文檔內容回答
142
+ 2. 如果文檔中沒有相關信息,請明確說明
143
+ 3. 引用具體的文檔內容來支持你的回答
144
+ 4. 用繁體中文回答
145
+ 5. 保持客觀和準確
146
+
147
+ 文檔內容:
148
+ {context}"""
149
+ },
150
  {"role": "user", "content": user_question}
151
  ],
152
  temperature=0.2
153
  )
154
+
155
  return response.choices[0].message.content
156
+
157
  except Exception as e:
158
+ error_msg = f"❌ 問答生成失敗: {str(e)}"
159
+ print(f"錯誤詳情: {traceback.format_exc()}")
160
+ return error_msg
161
 
162
  def clear_all():
163
+ """清除所有資料"""
164
  global summary_text, pdf_text
165
  summary_text = ""
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
+ )