diff --git a/Controller/YltAnalyticsController.py b/Controller/YltAnalyticsController.py index a6ff8ee..a72f853 100644 --- a/Controller/YltAnalyticsController.py +++ b/Controller/YltAnalyticsController.py @@ -13,6 +13,9 @@ from Tools.T6_Export import export_excel, DorisExcelExporter, extract_hourly_pri import tempfile import os import zipfile +import subprocess +from pydantic import BaseModel +from starlette.background import BackgroundTask from Model.YltAnalyticsModel import ( StationBase, CompetitorStation, @@ -95,6 +98,55 @@ async def export_prices_zip(): ) +class AiReportRequest(BaseModel): + content: str + + +@router.post("/api/export/ai-report-docx") +async def export_ai_report_docx(req: AiReportRequest): + content = req.content + if not content: + raise HTTPException(status_code=400, detail="Content is empty") + + # Create temp markdown file + with tempfile.NamedTemporaryFile(mode="w", suffix=".md", delete=False, encoding="utf-8") as tmp_md: + tmp_md.write(content) + tmp_md_path = tmp_md.name + + output_docx_path = tmp_md_path.replace(".md", ".docx") + + # Check template + template_path = "static/template/templates.docx" + cmd = ['pandoc', '-s', tmp_md_path, '-o', output_docx_path, '--resource-path=static'] + + # Only add reference doc if it exists, but the user requested it specifically. + # We'll check if it exists, if not, we might fail or warn, but let's try to include it if possible. + if os.path.exists(template_path): + cmd.extend(['--reference-doc', template_path]) + + try: + subprocess.run(cmd, check=True) + except subprocess.CalledProcessError as e: + # Clean up + if os.path.exists(tmp_md_path): + os.remove(tmp_md_path) + raise HTTPException(status_code=500, detail=f"Pandoc conversion failed: {str(e)}") + + def cleanup(): + if os.path.exists(tmp_md_path): + os.remove(tmp_md_path) + if os.path.exists(output_docx_path): + os.remove(output_docx_path) + + return FileResponse( + output_docx_path, + media_type="application/vnd.openxmlformats-officedocument.wordprocessingml.document", + filename="AI分析报告.docx", + background=BackgroundTask(cleanup) + ) + + + @router.get("/api/ai/pricing/strategy-summary") async def ai_pricing_strategy_summary(): async def generate_stream(): diff --git a/Controller/__pycache__/YltAnalyticsController.cpython-310.pyc b/Controller/__pycache__/YltAnalyticsController.cpython-310.pyc index 9c32356..4d27e2a 100644 Binary files a/Controller/__pycache__/YltAnalyticsController.cpython-310.pyc and b/Controller/__pycache__/YltAnalyticsController.cpython-310.pyc differ diff --git a/static/css/app.css b/static/css/app.css index 9e112a4..8d50364 100644 --- a/static/css/app.css +++ b/static/css/app.css @@ -278,6 +278,42 @@ body { margin-bottom: 12px; } +/* Markdown Table Styling in AI Result */ +.ai-result table { + width: 100%; + border-collapse: collapse; + margin: 16px 0; + font-size: 13px; + background-color: var(--card-bg); /* Use card bg to distinguish from ai-box bg */ + border: 1px solid var(--card-border); + border-radius: 6px; + overflow: hidden; /* For rounded corners on table */ +} + +.ai-result th, +.ai-result td { + padding: 10px 12px; + border: 1px solid var(--card-border); /* Explicit border color */ + text-align: left; +} + +.ai-result th { + background-color: rgba(15, 23, 42, 0.8); + color: var(--text-secondary); + font-weight: 600; + font-size: 12px; + text-transform: uppercase; + letter-spacing: 0.05em; +} + +.ai-result tr:nth-child(even) { + background-color: rgba(255, 255, 255, 0.02); /* Slight zebra striping */ +} + +.ai-result tr:hover td { + background-color: var(--table-row-hover); +} + /* Custom Scrollbar Webkit */ ::-webkit-scrollbar { width: 8px; diff --git a/static/index.html b/static/index.html index 047d7f9..096f201 100644 --- a/static/index.html +++ b/static/index.html @@ -15,9 +15,13 @@