feat: add rendering of markdown for the generated reports
This commit is contained in:
parent
df99d7deb2
commit
c2470c4833
11
README.md
11
README.md
|
|
@ -170,6 +170,17 @@ In addition to the CLI, a new web-based frontend is available to visualize the a
|
|||
4. Open your web browser and go to `http://127.0.0.1:8000`.
|
||||
5. Enter a company symbol (e.g., `AAPL`) in the configuration form and click "Start Process" to begin the analysis.
|
||||
|
||||
### Rendered Reports (Markdown Support)
|
||||
|
||||
Agent-generated reports (analysis summaries, debate histories, plans, and risk assessments) are produced in Markdown. The web frontend now renders these Markdown documents as styled HTML instead of showing raw markup. This includes support for:
|
||||
|
||||
- Headings, emphasis, lists, and blockquotes
|
||||
- Tables (for structured metrics)
|
||||
- Fenced code blocks and inline code
|
||||
|
||||
Security: Markdown is sanitized server‑side using `bleach` to strip unsafe tags/attributes while preserving semantic structure. If you need to extend allowed tags (e.g., to permit additional formatting), modify `ALLOWED_TAGS` / `ALLOWED_ATTRIBUTES` in `webapp/main.py`.
|
||||
|
||||
|
||||
## TradingAgents Package
|
||||
|
||||
### Implementation Details
|
||||
|
|
|
|||
Binary file not shown.
|
|
@ -37,4 +37,6 @@ dependencies = [
|
|||
"uvicorn",
|
||||
"python-multipart",
|
||||
"jinja2",
|
||||
"markdown>=3.6",
|
||||
"bleach>=6.1.0",
|
||||
]
|
||||
|
|
|
|||
|
|
@ -28,3 +28,5 @@ fastapi
|
|||
uvicorn
|
||||
python-multipart
|
||||
jinja2
|
||||
markdown
|
||||
bleach
|
||||
|
|
|
|||
|
|
@ -0,0 +1,14 @@
|
|||
from webapp.main import render_markdown
|
||||
|
||||
def test_markdown_basic_headers():
|
||||
md_text = "# Title\n\nSome **bold** text and a table:\n\n| Col1 | Col2 |\n| ---- | ---- |\n| A | B |\n"
|
||||
html = render_markdown(md_text)
|
||||
assert '<h1>' in html and 'Title' in html
|
||||
assert '<strong>' in html and 'bold' in html
|
||||
assert '<table>' in html
|
||||
|
||||
def test_markdown_code_block():
|
||||
md_text = "```python\nprint('hi')\n```"
|
||||
html = render_markdown(md_text)
|
||||
# Sanitized but should keep code element
|
||||
assert 'print' in html and '<code' in html
|
||||
|
|
@ -2,6 +2,8 @@ from fastapi import FastAPI, Request, Form, BackgroundTasks, HTTPException
|
|||
from fastapi.responses import HTMLResponse
|
||||
from fastapi.staticfiles import StaticFiles
|
||||
import jinja2
|
||||
import markdown as md
|
||||
import bleach
|
||||
import os
|
||||
from typing import Dict, Any
|
||||
import threading
|
||||
|
|
@ -56,6 +58,26 @@ app.mount("/static", StaticFiles(directory="webapp/static"), name="static")
|
|||
template_dir = os.path.join(os.path.dirname(__file__), "templates")
|
||||
jinja_env = jinja2.Environment(loader=jinja2.FileSystemLoader(template_dir))
|
||||
|
||||
# Allowed tags and attributes for sanitized markdown rendering
|
||||
ALLOWED_TAGS = list(bleach.sanitizer.ALLOWED_TAGS) + [
|
||||
"p", "pre", "span", "h1", "h2", "h3", "h4", "h5", "h6", "table", "thead", "tbody", "tr", "th", "td", "blockquote", "code"
|
||||
]
|
||||
ALLOWED_ATTRIBUTES = {**bleach.sanitizer.ALLOWED_ATTRIBUTES, "span": ["class"], "code": ["class"], "th": ["align"], "td": ["align"]}
|
||||
|
||||
def render_markdown(value: str) -> str:
|
||||
"""Convert markdown text to sanitized HTML."""
|
||||
if not isinstance(value, str):
|
||||
value = str(value)
|
||||
html = md.markdown(
|
||||
value,
|
||||
extensions=["fenced_code", "tables", "codehilite", "toc", "sane_lists"],
|
||||
output_format="html5"
|
||||
)
|
||||
cleaned = bleach.clean(html, tags=ALLOWED_TAGS, attributes=ALLOWED_ATTRIBUTES, strip=True)
|
||||
return cleaned
|
||||
|
||||
jinja_env.filters['markdown'] = render_markdown
|
||||
|
||||
def update_execution_state(state: Dict[str, Any]):
|
||||
"""Callback function to update the app_state based on LangGraph's state."""
|
||||
print(f"📡 Callback received state keys: {list(state.keys())}")
|
||||
|
|
|
|||
|
|
@ -398,6 +398,45 @@ body {
|
|||
word-wrap: break-word;
|
||||
}
|
||||
|
||||
/* Markdown content styling */
|
||||
.markdown-body {
|
||||
line-height: 1.6;
|
||||
font-size: 0.95rem;
|
||||
}
|
||||
.markdown-body h1, .markdown-body h2, .markdown-body h3 {
|
||||
border-bottom: 1px solid var(--border-color);
|
||||
padding-bottom: 0.3em;
|
||||
margin-top: 1.2em;
|
||||
}
|
||||
.markdown-body code {
|
||||
background: var(--bg-secondary);
|
||||
padding: 2px 5px;
|
||||
border-radius: 4px;
|
||||
font-family: 'Monaco', 'Menlo', 'Consolas', monospace;
|
||||
}
|
||||
.markdown-body pre code {
|
||||
display: block;
|
||||
padding: 12px;
|
||||
overflow-x: auto;
|
||||
}
|
||||
.markdown-body table {
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 1em 0;
|
||||
}
|
||||
.markdown-body th, .markdown-body td {
|
||||
border: 1px solid var(--border-color);
|
||||
padding: 6px 10px;
|
||||
text-align: left;
|
||||
}
|
||||
.markdown-body blockquote {
|
||||
border-left: 4px solid var(--accent-color);
|
||||
margin: 1em 0;
|
||||
padding: 0.5em 1em;
|
||||
background: var(--bg-secondary);
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
#right-panel h3 {
|
||||
color: var(--text-primary);
|
||||
margin-bottom: 15px;
|
||||
|
|
|
|||
|
|
@ -1,4 +1,4 @@
|
|||
<div>
|
||||
<h3>Content Details</h3>
|
||||
<pre>{{ content }}</pre>
|
||||
<div class="markdown-body">{{ content | markdown | safe }}</div>
|
||||
</div>
|
||||
Loading…
Reference in New Issue