优化pdf下载

This commit is contained in:
xucheng 2026-01-23 11:39:32 +08:00
parent 66f4914c5a
commit 30e954d89f
4 changed files with 63 additions and 10 deletions

View File

@ -236,7 +236,10 @@ class ExportPDFRequest(BaseModel):
async def export_chat_pdf(request: ExportPDFRequest, db: AsyncSession = Depends(get_db)): async def export_chat_pdf(request: ExportPDFRequest, db: AsyncSession = Depends(get_db)):
"""Export selected chat sessions to PDF""" """Export selected chat sessions to PDF"""
from sqlalchemy import select, desc, asc from sqlalchemy import select, desc, asc
from datetime import datetime from datetime import datetime, timezone, timedelta
# Timezone definition (Shanghai)
shanghai_tz = timezone(timedelta(hours=8))
if not request.session_ids: if not request.session_ids:
raise HTTPException(status_code=400, detail="No sessions selected") raise HTTPException(status_code=400, detail="No sessions selected")
@ -260,7 +263,13 @@ async def export_chat_pdf(request: ExportPDFRequest, db: AsyncSession = Depends(
messages_html = "" messages_html = ""
for log in logs: for log in logs:
timestamp = log.timestamp.strftime('%Y-%m-%d %H:%M:%S') if log.timestamp else "" # Convert timestamp to Shanghai time
if log.timestamp:
ts_shanghai = log.timestamp.astimezone(shanghai_tz) if log.timestamp.tzinfo else log.timestamp.replace(tzinfo=timezone.utc).astimezone(shanghai_tz)
timestamp = ts_shanghai.strftime('%Y-%m-%d %H:%M:%S')
else:
timestamp = ""
response_html = markdown.markdown(log.response or "", extensions=['tables', 'fenced_code']) response_html = markdown.markdown(log.response or "", extensions=['tables', 'fenced_code'])
# Add context info (Stock Code / Session) to the header since they are mixed # Add context info (Stock Code / Session) to the header since they are mixed
@ -275,7 +284,6 @@ async def export_chat_pdf(request: ExportPDFRequest, db: AsyncSession = Depends(
</div> </div>
<div class="message-sub-meta"> <div class="message-sub-meta">
<span class="session-id">Session: {session_info}</span> <span class="session-id">Session: {session_info}</span>
<span class="model">Model: {log.model}</span>
</div> </div>
<div class="message-content"> <div class="message-content">
{response_html} {response_html}
@ -284,6 +292,8 @@ async def export_chat_pdf(request: ExportPDFRequest, db: AsyncSession = Depends(
''' '''
# Complete HTML # Complete HTML
now_shanghai = datetime.now(shanghai_tz)
# Updated font-family stack to include common Linux/Windows CJK fonts # Updated font-family stack to include common Linux/Windows CJK fonts
complete_html = f''' complete_html = f'''
<!DOCTYPE html> <!DOCTYPE html>
@ -383,7 +393,7 @@ async def export_chat_pdf(request: ExportPDFRequest, db: AsyncSession = Depends(
</head> </head>
<body> <body>
<h1>AI 研究讨论导出</h1> <h1>AI 研究讨论导出</h1>
<p style="text-align: center; color: #666; margin-bottom: 30px;">导出时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}</p> <p style="text-align: center; color: #666; margin-bottom: 30px;">导出时间: {now_shanghai.strftime('%Y-%m-%d %H:%M:%S')}</p>
{messages_html} {messages_html}
</body> </body>
</html> </html>
@ -395,7 +405,13 @@ async def export_chat_pdf(request: ExportPDFRequest, db: AsyncSession = Depends(
pdf_path = tmp_file.name pdf_path = tmp_file.name
HTML(string=complete_html).write_pdf(pdf_path) HTML(string=complete_html).write_pdf(pdf_path)
filename = f"AI研究讨论汇总_{datetime.now().strftime('%Y%m%d_%H%M%S')}.pdf" # Determine filename prefix based on the most common stock code in logs
# or just the first one found
company_name = "AI研究讨论"
if logs and logs[0].stock_code:
company_name = logs[0].stock_code
filename = f"{company_name}_{now_shanghai.strftime('%Y%m%d')}_讨论记录.pdf"
filename_encoded = quote(filename) filename_encoded = quote(filename)
return FileResponse( return FileResponse(

View File

@ -343,6 +343,17 @@ async def download_report_pdf(report_id: int, db: AsyncSession = Depends(get_db)
""" """
# Complete PDF HTML # Complete PDF HTML
# Timezone conversion for display
from datetime import datetime, timezone, timedelta
shanghai_tz = timezone(timedelta(hours=8))
# Use report creation time for the report date in PDF
report_time = report.created_at.astimezone(shanghai_tz) if report.created_at.tzinfo else report.created_at.replace(tzinfo=timezone.utc).astimezone(shanghai_tz)
# Current time for filename (download date)
now_shanghai = datetime.now(shanghai_tz)
download_date_str = now_shanghai.strftime('%Y%m%d')
complete_html = f""" complete_html = f"""
<!DOCTYPE html> <!DOCTYPE html>
<html> <html>
@ -515,8 +526,7 @@ async def download_report_pdf(report_id: int, db: AsyncSession = Depends(get_db)
<div class="cover"> <div class="cover">
<h1>{report.company_name}</h1> <h1>{report.company_name}</h1>
<div class="meta">{report.market} {report.symbol}</div> <div class="meta">{report.market} {report.symbol}</div>
<div class="meta">分析日期: {report.created_at.strftime('%Y年%m月%d')}</div> <div class="meta">分析日期: {report_time.strftime('%Y年%m月%d')}</div>
{f'<div class="meta">AI模型: {report.ai_model}</div>' if report.ai_model else ''}
</div> </div>
<div class="section"> <div class="section">
@ -538,7 +548,8 @@ async def download_report_pdf(report_id: int, db: AsyncSession = Depends(get_db)
HTML(string=complete_html).write_pdf(pdf_path) HTML(string=complete_html).write_pdf(pdf_path)
# Return the PDF file # Return the PDF file
filename = f"{report.company_name}_{report.symbol}_分析报告.pdf" # Filename format: Company_Symbol_Date_Report.pdf
filename = f"{report.company_name}_{report.symbol}_{download_date_str}_分析报告.pdf"
# Use RFC 5987 encoding for non-ASCII filenames # Use RFC 5987 encoding for non-ASCII filenames
filename_encoded = quote(filename) filename_encoded = quote(filename)

View File

@ -75,7 +75,14 @@ export default function AnalysisPage({ params }: { params: Promise<{ id: string
const url = window.URL.createObjectURL(blob) const url = window.URL.createObjectURL(blob)
const a = document.createElement('a') const a = document.createElement('a')
a.href = url a.href = url
a.download = `${report.company_name}_${report.symbol}_分析报告.pdf`
// Format date as YYYYMMDD to match backend
const date = new Date()
const dateStr = date.getFullYear() +
String(date.getMonth() + 1).padStart(2, '0') +
String(date.getDate()).padStart(2, '0')
a.download = `${report.company_name}_${report.symbol}_${dateStr}_分析报告.pdf`
document.body.appendChild(a) document.body.appendChild(a)
a.click() a.click()
window.URL.revokeObjectURL(url) window.URL.revokeObjectURL(url)

View File

@ -250,11 +250,30 @@ export function HistoryView() {
body: JSON.stringify({ session_ids: Array.from(selectedForExport) }) body: JSON.stringify({ session_ids: Array.from(selectedForExport) })
}) })
if (!response.ok) throw new Error("导出失败") if (!response.ok) throw new Error("导出失败")
// Try to get filename from content-disposition
let filename = `讨论历史_${new Date().toISOString().slice(0, 10)}.pdf`
const disposition = response.headers.get('content-disposition')
if (disposition && disposition.indexOf('attachment') !== -1) {
const filenameRegex = /filename\*=UTF-8''([\w%\-\.]+)(?:;|$)|filename="([^"]+)"(?:;|$)|filename=([^;]+)(?:;|$)/i;
const matches = filenameRegex.exec(disposition);
if (matches) {
// Try decoding the UTF-8 encoded filename
try {
if (matches[1]) filename = decodeURIComponent(matches[1]);
else if (matches[2]) filename = matches[2];
else if (matches[3]) filename = matches[3];
} catch (e) {
console.error("Error decoding filename:", e);
}
}
}
const blob = await response.blob() const blob = await response.blob()
const url = window.URL.createObjectURL(blob) const url = window.URL.createObjectURL(blob)
const a = document.createElement("a") const a = document.createElement("a")
a.href = url a.href = url
a.download = `讨论历史_${new Date().toISOString().slice(0, 10)}.pdf` a.download = filename
document.body.appendChild(a) document.body.appendChild(a)
a.click() a.click()
document.body.removeChild(a) document.body.removeChild(a)