- alphaear-deepear-lite: DeepEar Lite API integration - alphaear-logic-visualizer: Draw.io XML finance diagrams - alphaear-news: Real-time finance news (10+ sources) - alphaear-predictor: Kronos time-series forecasting - alphaear-reporter: Professional financial reports - alphaear-search: Web search + local RAG - alphaear-sentiment: FinBERT/LLM sentiment analysis - alphaear-signal-tracker: Signal evolution tracking - alphaear-stock: A-Share/HK/US stock data Updates: - All scripts updated to use universal .env path - Added JINA_API_KEY, LLM_*, DEEPSEEK_API_KEY to .env.example - Updated load_dotenv() to use ~/.config/opencode/.env
186 lines
4.8 KiB
Python
186 lines
4.8 KiB
Python
import markdown
|
|
import os
|
|
from loguru import logger
|
|
|
|
def convert_md_to_html(md_content: str, title: str = "AlphaEar Report") -> str:
|
|
"""
|
|
将 Markdown 转换为带样式的 HTML
|
|
"""
|
|
# 转换 Markdown 为 HTML
|
|
# 启用 table, toc 等扩展
|
|
# 使用 'md_in_html' 来正确处理 markdown 中的 HTML 块
|
|
html_body = markdown.markdown(
|
|
md_content,
|
|
extensions=['extra', 'toc', 'nl2br', 'md_in_html']
|
|
)
|
|
|
|
|
|
# 简单的 Premium CSS 模板
|
|
html_template = f"""
|
|
<!DOCTYPE html>
|
|
<html lang="zh-CN">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>{title}</title>
|
|
<style>
|
|
:root {{
|
|
--primary-color: #2c3e50;
|
|
--secondary-color: #34495e;
|
|
--accent-color: #3498db;
|
|
--bg-color: #f4f7f6;
|
|
--text-color: #333;
|
|
--white: #ffffff;
|
|
}}
|
|
|
|
body {{
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
line-height: 1.6;
|
|
color: var(--text-color);
|
|
background-color: var(--bg-color);
|
|
margin: 0;
|
|
padding: 0;
|
|
}}
|
|
|
|
.container {{
|
|
max-width: 900px;
|
|
margin: 40px auto;
|
|
background: var(--white);
|
|
padding: 40px;
|
|
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
|
border-radius: 12px;
|
|
}}
|
|
|
|
h1, h2, h3, h4 {{
|
|
color: var(--primary-color);
|
|
margin-top: 1.5em;
|
|
}}
|
|
|
|
h1 {{
|
|
text-align: center;
|
|
border-bottom: 2px solid var(--accent-color);
|
|
padding-bottom: 10px;
|
|
}}
|
|
|
|
h2 {{
|
|
border-left: 5px solid var(--accent-color);
|
|
padding-left: 15px;
|
|
background: rgba(52, 152, 219, 0.05);
|
|
padding-top: 10px;
|
|
padding-bottom: 10px;
|
|
}}
|
|
|
|
code {{
|
|
background: #f0f0f0;
|
|
padding: 2px 5px;
|
|
border-radius: 4px;
|
|
font-family: 'Courier New', Courier, monospace;
|
|
}}
|
|
|
|
pre {{
|
|
background: #282c34;
|
|
color: #abb2bf;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
overflow-x: auto;
|
|
}}
|
|
|
|
blockquote {{
|
|
border-left: 4px solid #ccc;
|
|
margin: 1.5em 10px;
|
|
padding: 0.5em 10px;
|
|
color: #666;
|
|
font-style: italic;
|
|
}}
|
|
|
|
table {{
|
|
width: 100%;
|
|
border-collapse: collapse;
|
|
margin: 20px 0;
|
|
}}
|
|
|
|
table th, table td {{
|
|
border: 1px solid #ddd;
|
|
padding: 12px;
|
|
text-align: left;
|
|
}}
|
|
|
|
table th {{
|
|
background-color: #f8f9fa;
|
|
}}
|
|
|
|
.toc {{
|
|
background: #f9f9f9;
|
|
padding: 20px;
|
|
border-radius: 8px;
|
|
margin-bottom: 30px;
|
|
border: 1px solid #eee;
|
|
}}
|
|
|
|
iframe {{
|
|
margin: 20px 0;
|
|
border-radius: 8px;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.05);
|
|
}}
|
|
|
|
/* 响应式样式 */
|
|
@media (max-width: 600px) {{
|
|
.container {{
|
|
margin: 0;
|
|
padding: 20px;
|
|
border-radius: 0;
|
|
}}
|
|
}}
|
|
|
|
.footer {{
|
|
text-align: center;
|
|
margin-top: 50px;
|
|
color: #999;
|
|
font-size: 0.9em;
|
|
}}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<div class="container">
|
|
{html_body}
|
|
<div class="footer">
|
|
<p>Generated by AlphaEar @ {os.popen('date').read().strip()}</p>
|
|
</div>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
"""
|
|
return html_template
|
|
|
|
def save_report_as_html(md_path: str, output_path: str = None):
|
|
if not output_path:
|
|
output_path = md_path.replace(".md", ".html")
|
|
|
|
try:
|
|
with open(md_path, "r", encoding="utf-8") as f:
|
|
md_content = f.read()
|
|
|
|
title = "AlphaEar 市场研报"
|
|
# 尝试从第一行获取标题
|
|
lines = md_content.split('\n')
|
|
if lines and lines[0].startswith('# '):
|
|
title = lines[0].replace('# ', '').strip()
|
|
|
|
html_content = convert_md_to_html(md_content, title)
|
|
|
|
with open(output_path, "w", encoding="utf-8") as f:
|
|
f.write(html_content)
|
|
|
|
logger.info(f"✅ HTML Report saved to: {output_path}")
|
|
return output_path
|
|
except Exception as e:
|
|
logger.error(f"Failed to convert report to HTML: {e}")
|
|
return None
|
|
|
|
if __name__ == "__main__":
|
|
import sys
|
|
if len(sys.argv) > 1:
|
|
save_report_as_html(sys.argv[1])
|
|
else:
|
|
print("Usage: python3 md_to_html.py <path_to_md>")
|