diff --git a/backend/app/api/chat_routes.py b/backend/app/api/chat_routes.py index a761f43..b640446 100644 --- a/backend/app/api/chat_routes.py +++ b/backend/app/api/chat_routes.py @@ -236,7 +236,10 @@ class ExportPDFRequest(BaseModel): async def export_chat_pdf(request: ExportPDFRequest, db: AsyncSession = Depends(get_db)): """Export selected chat sessions to PDF""" 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: 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 = "" 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']) # 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(
Session: {session_info} - Model: {log.model}
{response_html} @@ -284,6 +292,8 @@ async def export_chat_pdf(request: ExportPDFRequest, db: AsyncSession = Depends( ''' # Complete HTML + now_shanghai = datetime.now(shanghai_tz) + # Updated font-family stack to include common Linux/Windows CJK fonts complete_html = f''' @@ -383,7 +393,7 @@ async def export_chat_pdf(request: ExportPDFRequest, db: AsyncSession = Depends(

AI 研究讨论导出

-

导出时间: {datetime.now().strftime('%Y-%m-%d %H:%M:%S')}

+

导出时间: {now_shanghai.strftime('%Y-%m-%d %H:%M:%S')}

{messages_html} @@ -395,7 +405,13 @@ async def export_chat_pdf(request: ExportPDFRequest, db: AsyncSession = Depends( pdf_path = tmp_file.name 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) return FileResponse( diff --git a/backend/app/api/routes.py b/backend/app/api/routes.py index 6547b22..8f4dcfe 100644 --- a/backend/app/api/routes.py +++ b/backend/app/api/routes.py @@ -343,6 +343,17 @@ async def download_report_pdf(report_id: int, db: AsyncSession = Depends(get_db) """ # 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""" @@ -515,8 +526,7 @@ async def download_report_pdf(report_id: int, db: AsyncSession = Depends(get_db)

{report.company_name}

{report.market} {report.symbol}
-
分析日期: {report.created_at.strftime('%Y年%m月%d日')}
- {f'
AI模型: {report.ai_model}
' if report.ai_model else ''} +
分析日期: {report_time.strftime('%Y年%m月%d日')}
@@ -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) # 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 filename_encoded = quote(filename) diff --git a/frontend/src/app/analysis/[id]/page.tsx b/frontend/src/app/analysis/[id]/page.tsx index 85fe8be..3b243f3 100644 --- a/frontend/src/app/analysis/[id]/page.tsx +++ b/frontend/src/app/analysis/[id]/page.tsx @@ -75,7 +75,14 @@ export default function AnalysisPage({ params }: { params: Promise<{ id: string const url = window.URL.createObjectURL(blob) const a = document.createElement('a') 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) a.click() window.URL.revokeObjectURL(url) diff --git a/frontend/src/components/history-view.tsx b/frontend/src/components/history-view.tsx index 27316c4..b00d010 100644 --- a/frontend/src/components/history-view.tsx +++ b/frontend/src/components/history-view.tsx @@ -250,11 +250,30 @@ export function HistoryView() { body: JSON.stringify({ session_ids: Array.from(selectedForExport) }) }) 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 url = window.URL.createObjectURL(blob) const a = document.createElement("a") a.href = url - a.download = `讨论历史_${new Date().toISOString().slice(0, 10)}.pdf` + a.download = filename document.body.appendChild(a) a.click() document.body.removeChild(a)