3a05chatgpt's picture
Update app.py
228df30 verified
raw
history blame
8.6 kB
import openai
import gradio as gr
import fitz # PyMuPDF
from openai import OpenAI
import traceback
# 全域變數
api_key = ""
selected_model = "gpt-4"
summary_text = ""
client = None
pdf_text = ""
def set_api_key(user_api_key):
"""設定 OpenAI API Key 並初始化客戶端"""
global api_key, client
try:
api_key = user_api_key.strip()
if not api_key:
return "❌ API Key 不能為空"
if not api_key.startswith('sk-'):
return "❌ API Key 格式錯誤,應該以 'sk-' 開頭"
client = OpenAI(api_key=api_key)
# 測試 API Key 是否有效
try:
test_response = client.chat.completions.create(
model="gpt-3.5-turbo",
messages=[{"role": "user", "content": "Hello"}],
max_tokens=5
)
return "✅ API Key 已設定並驗證成功!可以開始使用了。"
except Exception as e:
if "incorrect_api_key" in str(e).lower():
return "❌ API Key 無效,請檢查是否正確"
elif "quota" in str(e).lower():
return "⚠️ API Key 有效,但配額不足"
else:
return f"⚠️ API Key 設定成功,但測試時出現問題:{str(e)[:100]}"
except Exception as e:
return f"❌ 設定失敗:{str(e)}"
def set_model(model_name):
"""設定選擇的模型"""
global selected_model
if model_name:
selected_model = model_name
return f"✅ 模型已更新為:{model_name}"
else:
return "❌ 請選擇一個模型"
def extract_pdf_text(file_path):
"""從 PDF 文件中提取文字"""
try:
doc = fitz.open(file_path)
text = ""
for page_num, page in enumerate(doc):
page_text = page.get_text()
if page_text.strip():
text += f"\n--- 第 {page_num + 1} 頁 ---\n"
text += page_text
doc.close()
return text
except Exception as e:
return f"❌ PDF 解析錯誤: {str(e)}"
def generate_summary(pdf_file):
"""從 PDF 內容生成摘要"""
global summary_text, pdf_text
if not client:
return "❌ 請先設定 OpenAI API Key"
if not pdf_file:
return "❌ 請先上傳 PDF 文件"
try:
pdf_text = extract_pdf_text(pdf_file.name)
if not pdf_text.strip():
return "⚠️ 無法解析 PDF 文字,可能為純圖片 PDF 或空白文件。"
max_chars = 8000
if len(pdf_text) > max_chars:
pdf_text_truncated = pdf_text[:max_chars] + "\n\n[文本已截斷,僅顯示前 8000 字符]"
else:
pdf_text_truncated = pdf_text
response = client.chat.completions.create(
model=selected_model,
messages=[
{
"role": "system",
"content": """你是一個專業的文檔摘要助手。請將以下 PDF 內容整理為結構化的摘要:
1. 首先提供一個簡短的總體概述
2. 然後按照重要性列出主要重點(使用項目符號)
3. 如果有數據或統計信息,請特別標注
4. 如果有結論或建議,請單獨列出
請用繁體中文回答,保持專業且易於理解的語調。"""
},
{"role": "user", "content": pdf_text_truncated}
],
temperature=0.3
)
summary_text = response.choices[0].message.content
return summary_text
except Exception as e:
error_msg = f"❌ 摘要生成失敗: {str(e)}"
print(f"錯誤詳情: {traceback.format_exc()}")
return error_msg
def ask_question(user_question):
"""基於 PDF 內容回答問題"""
if not client:
return "❌ 請先設定 OpenAI API Key"
if not summary_text and not pdf_text:
return "❌ 請先生成 PDF 摘要"
if not user_question.strip():
return "❌ 請輸入問題"
try:
context = f"PDF 摘要:\n{summary_text}\n\n原始內容(部分):\n{pdf_text[:2000]}"
response = client.chat.completions.create(
model=selected_model,
messages=[
{
"role": "system",
"content": f"""你是一個專業的文檔問答助手。請基於提供的 PDF 內容回答用戶問題。
規則:
1. 只根據提供的文檔內容回答
2. 如果文檔中沒有相關信息,請明確說明
3. 引用具體的文檔內容來支持你的回答
4. 用繁體中文回答
5. 保持客觀和準確
文檔內容:
{context}"""
},
{"role": "user", "content": user_question}
],
temperature=0.2
)
return response.choices[0].message.content
except Exception as e:
error_msg = f"❌ 問答生成失敗: {str(e)}"
print(f"錯誤詳情: {traceback.format_exc()}")
return error_msg
def clear_all():
"""清除所有資料"""
global summary_text, pdf_text
summary_text = ""
pdf_text = ""
return "", "", ""
# 創建最簡單的 Gradio 介面
with gr.Blocks(title="PDF 摘要助手") as demo:
gr.Markdown("# 📄 PDF 摘要 & 問答助手")
with gr.Tab("🔧 設定"):
gr.Markdown("## API Key 設定")
api_key_input = gr.Textbox(
label="輸入 OpenAI API Key",
type="password",
placeholder="請輸入您的 OpenAI API Key (sk-...)"
)
with gr.Row():
api_key_btn = gr.Button("確認設定 API Key", variant="primary")
api_key_status = gr.Textbox(
label="API 狀態",
interactive=False,
value="等待設定 API Key..."
)
gr.Markdown("## 模型選擇")
model_choice = gr.Radio(
choices=["gpt-4", "gpt-4.1", "gpt-4.5"],
label="選擇 AI 模型",
value="gpt-4"
)
with gr.Row():
model_btn = gr.Button("確認選擇模型", variant="secondary")
model_status = gr.Textbox(
label="當前模型",
interactive=False,
value="已選擇:gpt-4"
)
with gr.Tab("📄 PDF 處理"):
gr.Markdown("## 上傳 PDF 文件")
pdf_upload = gr.File(
label="選擇 PDF 文件",
file_types=[".pdf"]
)
with gr.Row():
summary_btn = gr.Button("生成摘要", variant="primary")
clear_btn = gr.Button("清除資料", variant="secondary")
summary_output = gr.Textbox(
label="PDF 摘要",
lines=15,
placeholder="上傳 PDF 文件並點擊「生成摘要」按鈕"
)
with gr.Tab("❓ 問答"):
gr.Markdown("## AI 問答")
question_input = gr.Textbox(
label="請輸入您的問題",
placeholder="例如:這份文件的主要結論是什麼?",
lines=3
)
with gr.Row():
question_btn = gr.Button("發送問題", variant="primary")
answer_output = gr.Textbox(
label="AI 回答",
lines=10,
placeholder="AI 回答將顯示在這裡"
)
# 事件綁定 - 使用最簡單的方式
api_key_btn.click(
fn=set_api_key,
inputs=api_key_input,
outputs=api_key_status
)
model_btn.click(
fn=set_model,
inputs=model_choice,
outputs=model_status
)
summary_btn.click(
fn=generate_summary,
inputs=pdf_upload,
outputs=summary_output
)
question_btn.click(
fn=ask_question,
inputs=question_input,
outputs=answer_output
)
clear_btn.click(
fn=clear_all,
outputs=[summary_output, question_input, answer_output]
)
# 也支援 Enter 鍵
api_key_input.submit(
fn=set_api_key,
inputs=api_key_input,
outputs=api_key_status
)
question_input.submit(
fn=ask_question,
inputs=question_input,
outputs=answer_output
)
if __name__ == "__main__":
demo.launch(
share=False,
show_api=False,
show_error=True
)