Ai-Teacher/server.py

237 lines
8.6 KiB
Python
Raw Normal View History

2025-03-07 00:00:11 +08:00
import os
import json
import logging
2025-03-07 00:00:11 +08:00
import asyncio
from flask import Flask, request, jsonify, send_from_directory
from flask_cors import CORS
import openai
import fitz # PyMuPDF
from dotenv import load_dotenv
# 加载环境变量
load_dotenv()
# 配置日志
logging.basicConfig(
level=logging.INFO,
format='%(asctime)s - %(name)s - %(levelname)s - %(message)s',
handlers=[
logging.StreamHandler()
]
)
logger = logging.getLogger(__name__)
2025-03-07 00:00:11 +08:00
# 获取API密钥
#openai_api_key = os.getenv("OPENAI_API_KEY")
openai_api_key = "sk-95ab48a1e0754ad39c13e2987f73fe37"
openai_base_url = "https://api.deepseek.com"
if not openai_api_key:
logger.warning("OpenAI API key not found. AI explanation will use fallback mode.")
# 加载设置
try:
with open('setting.json', 'r') as f:
settings = json.load(f)
port = settings.get('websocket_port', 6006)
2025-03-07 00:00:11 +08:00
except Exception as e:
logger.error(f"Error loading settings: {e}")
port = 6006
2025-03-07 00:00:11 +08:00
app = Flask(__name__, static_url_path='')
CORS(app)
2025-03-07 00:00:11 +08:00
# 存储PDF文档内容和生成的讲解
pdf_content = {
"full_text": "",
"pages": [],
"explanations": []
}
def extract_pdf_text(pdf_path):
"""提取PDF文档的全部文本内容和每一页的文本"""
try:
doc = fitz.open(pdf_path)
full_text = ""
pages = []
for page_num in range(len(doc)):
page = doc.load_page(page_num)
page_text = page.get_text()
pages.append(page_text)
full_text += f"\n--- 第{page_num+1}页 ---\n{page_text}"
return {
"success": True,
"full_text": full_text,
"pages": pages,
"page_count": len(doc)
}
except Exception as e:
logger.error(f"Error extracting PDF text: {e}")
return {
"success": False,
"error": str(e)
}
2025-03-07 00:00:11 +08:00
async def generate_explanations_for_all_pages(full_text, pages):
"""为所有页面生成讲解内容"""
explanations = []
client = openai.OpenAI(api_key=openai_api_key, base_url=openai_base_url)
# 首先让LLM理解整个文档
try:
2025-03-07 00:00:11 +08:00
logger.info("Generating context understanding from full document...")
context_response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "你是一位专业的教师需要理解整个PDF文档的内容以便后续为每一页生成讲解。"},
{"role": "user", "content": f"请阅读并理解以下PDF文档的全部内容不需要回复具体内容只需要理解\n\n{full_text}"}
]
)
context_understanding = context_response.choices[0].message.content.strip()
logger.info("Context understanding generated successfully")
except Exception as e:
logger.error(f"Error generating context understanding: {e}")
context_understanding = "无法生成文档理解,将基于单页内容生成讲解。"
# 为每一页生成讲解
for i, page_text in enumerate(pages):
try:
logger.info(f"Generating explanation for page {i+1}...")
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": f"你是一位专业的教师正在为学生讲解PDF文档内容。你已经理解了整个文档的内容现在需要为第{i+1}页生成简洁的讲解。请提供清晰、简洁的解释,重点突出关键概念。你的讲解应该考虑到整个文档的上下文,而不仅仅是孤立地解释当前页面。"},
{"role": "user", "content": f"基于你对整个文档的理解,请为第{i+1}页生成简洁的讲解:\n\n{page_text}"}
]
)
explanation = response.choices[0].message.content.strip()
explanations.append(explanation)
logger.info(f"Explanation for page {i+1} generated successfully")
except Exception as e:
logger.error(f"Error generating explanation for page {i+1}: {e}")
explanations.append(f"生成第{i+1}页讲解时出错: {str(e)}")
return explanations
def generate_explanation(page_text, page_num=None):
"""为单个页面生成讲解内容"""
if not openai_api_key:
return "这是一个示例讲解。请设置OpenAI API密钥以获取真实的AI讲解内容。"
# 如果已经有预生成的讲解,直接返回
if pdf_content["explanations"] and page_num is not None and 0 <= page_num-1 < len(pdf_content["explanations"]):
return pdf_content["explanations"][page_num-1]
try:
client = openai.OpenAI(api_key=openai_api_key, base_url=openai_base_url)
response = client.chat.completions.create(
model="deepseek-chat",
messages=[
{"role": "system", "content": "你是一位专业的教师正在为学生讲解PDF文档内容。请提供清晰、简洁的解释重点突出关键概念。"},
{"role": "user", "content": f"请讲解以下内容:\n\n{page_text}"}
]
)
return response.choices[0].message.content.strip()
except Exception as e:
logger.error(f"Error generating explanation: {e}")
return f"生成讲解时出错: {str(e)}"
@app.route('/')
def index():
return send_from_directory('', 'index.html')
@app.route('/<path:path>')
def serve_static(path):
return send_from_directory('', path)
@app.route('/api/explain', methods=['POST'])
def explain():
data = request.json
text = data.get('text', '')
page_num = data.get('page', None)
explanation = generate_explanation(text, page_num)
return jsonify({'explanation': explanation})
@app.route('/api/load_pdf', methods=['POST'])
def load_pdf():
data = request.json
pdf_path = data.get('path', './public/pdf/VLA4RM-仿生智能.pdf')
2025-03-07 00:00:11 +08:00
# 提取PDF文本
result = extract_pdf_text(pdf_path)
if result["success"]:
# 更新全局PDF内容
pdf_content["full_text"] = result["full_text"]
pdf_content["pages"] = result["pages"]
# 异步生成所有页面的讲解
async def process_explanations():
explanations = await generate_explanations_for_all_pages(
result["full_text"],
result["pages"]
)
pdf_content["explanations"] = explanations
logger.info(f"Generated explanations for all {len(explanations)} pages")
# 启动异步任务
asyncio.run(process_explanations())
return jsonify({
'success': True,
'message': '已加载PDF并开始生成讲解',
'page_count': result["page_count"]
})
else:
return jsonify({
'success': False,
'message': f'加载PDF失败: {result["error"]}'
})
@app.route('/api/get_explanation/<int:page_num>', methods=['GET'])
def get_explanation(page_num):
if 0 <= page_num-1 < len(pdf_content["explanations"]):
return jsonify({
'success': True,
'explanation': pdf_content["explanations"][page_num-1]
})
else:
return jsonify({
'success': False,
'message': f'页码 {page_num} 的讲解不存在'
})
@app.route('/api/explanation_status', methods=['GET'])
def explanation_status():
return jsonify({
'total_pages': len(pdf_content["pages"]),
'explanations_generated': len(pdf_content["explanations"]),
'is_complete': len(pdf_content["pages"]) > 0 and len(pdf_content["pages"]) == len(pdf_content["explanations"])
})
if __name__ == '__main__':
# 在启动时预加载默认PDF
default_pdf_path = './VLA4RM-仿生智能.pdf'
if os.path.exists(default_pdf_path):
logger.info(f"Pre-loading default PDF: {default_pdf_path}")
result = extract_pdf_text(default_pdf_path)
if result["success"]:
pdf_content["full_text"] = result["full_text"]
pdf_content["pages"] = result["pages"]
# 异步生成所有页面的讲解
async def process_explanations():
explanations = await generate_explanations_for_all_pages(
result["full_text"],
result["pages"]
)
pdf_content["explanations"] = explanations
logger.info(f"Generated explanations for all {len(explanations)} pages")
# 启动异步任务
asyncio.run(process_explanations())
app.run(host='0.0.0.0', port=port, debug=True)