Spaces:
Runtime error
Runtime error
Update app.py
Browse files
app.py
CHANGED
|
@@ -33,59 +33,66 @@ def call_visual_llm(prompt: str) -> str:
|
|
| 33 |
# Replace 'openai_call' with your actual API call function.
|
| 34 |
response = openai_call(prompt, model="o3-mini", max_tokens_param=10000)
|
| 35 |
# Remove code fences if the model returns them.
|
| 36 |
-
response = response.strip().strip("```").strip(
|
| 37 |
return response
|
| 38 |
|
| 39 |
def generate_visual_snippet(placeholder_text: str, context: str, initial_query: str, crumbs: str) -> str:
|
| 40 |
prompt = (f"""
|
| 41 |
-
Generate a complete, self-contained HTML code snippet that includes inline CSS and JavaScript
|
| 42 |
The code should display a simple but effective and elegant visualization based on the following requirements:
|
| 43 |
{placeholder_text}
|
| 44 |
|
| 45 |
-
//
|
| 46 |
-
|
| 47 |
-
-
|
| 48 |
-
|
| 49 |
-
-
|
| 50 |
-
|
| 51 |
-
-
|
| 52 |
-
|
| 53 |
-
|
| 54 |
-
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
-
|
| 58 |
-
-
|
| 59 |
-
-
|
| 60 |
-
|
| 61 |
-
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
/
|
| 65 |
-
|
| 66 |
-
|
| 67 |
-
|
| 68 |
-
|
| 69 |
-
|
|
|
|
|
|
|
|
|
|
| 70 |
)
|
| 71 |
result = call_visual_llm(prompt)
|
| 72 |
-
logging.info(f"The code produced for this visual placeholder:\n{placeholder_text}\n\n
|
| 73 |
return result
|
| 74 |
|
| 75 |
def replace_visual_placeholders(report_html: str, context: str, initial_query: str, crumbs: str) -> str:
|
| 76 |
-
pattern = r"\[\[Visual Placeholder \d+:(.*?)\]\]" #
|
|
|
|
| 77 |
|
| 78 |
def placeholder_replacer(match):
|
| 79 |
-
|
| 80 |
-
|
|
|
|
| 81 |
|
| 82 |
-
|
| 83 |
-
|
| 84 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 85 |
|
| 86 |
-
|
| 87 |
-
new_report_html = re.sub(pattern, placeholder_replacer, report_html, flags=re.DOTALL)
|
| 88 |
-
return new_report_html
|
| 89 |
|
| 90 |
def get_random_header():
|
| 91 |
headers = [
|
|
@@ -617,6 +624,15 @@ def refine_query(query: str, openai_api_key: str) -> str:
|
|
| 617 |
logging.info(f"refine_query: Refined query: {refined}")
|
| 618 |
return refined
|
| 619 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 620 |
class ReportGenerator:
|
| 621 |
def __init__(self):
|
| 622 |
pass
|
|
@@ -676,18 +692,46 @@ class ReportGenerator:
|
|
| 676 |
logging.info("ReportGenerator: HTML report generated successfully.")
|
| 677 |
return full_html
|
| 678 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 679 |
def generate_report_pdf(self, solution_content: str, metadata: dict = None) -> bytes:
|
| 680 |
-
|
| 681 |
-
Generate a PDF report by first creating the HTML report and converting it with xhtml2pdf.
|
| 682 |
-
"""
|
| 683 |
-
# Create the HTML content
|
| 684 |
html_report = self.generate_report_html(solution_content, metadata)
|
| 685 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 686 |
pdf_buffer = io.BytesIO()
|
| 687 |
pisa_status = pisa.CreatePDF(html_report, dest=pdf_buffer)
|
|
|
|
|
|
|
| 688 |
if pisa_status.err:
|
| 689 |
-
|
| 690 |
-
|
|
|
|
| 691 |
return pdf_buffer.getvalue()
|
| 692 |
|
| 693 |
def handle_generate_report(query_name: str, user_name: str, final_report: str):
|
|
@@ -882,8 +926,6 @@ def iterative_deep_research_gen(initial_query: str, reportstyle: str, breadth: i
|
|
| 882 |
|
| 883 |
alignment_assessment = assess_report_alignment(final_report, initial_query, followup_clarifications)
|
| 884 |
final_report += f"""
|
| 885 |
-
|
| 886 |
-
|
| 887 |
|
| 888 |
<p><b>Report alignment assessment:</b>
|
| 889 |
{alignment_assessment}</p>"""
|
|
|
|
| 33 |
# Replace 'openai_call' with your actual API call function.
|
| 34 |
response = openai_call(prompt, model="o3-mini", max_tokens_param=10000)
|
| 35 |
# Remove code fences if the model returns them.
|
| 36 |
+
response = response.strip().strip("```").strip()
|
| 37 |
return response
|
| 38 |
|
| 39 |
def generate_visual_snippet(placeholder_text: str, context: str, initial_query: str, crumbs: str) -> str:
|
| 40 |
prompt = (f"""
|
| 41 |
+
Generate a complete, self-contained HTML code snippet that includes inline CSS and JavaScript.
|
| 42 |
The code should display a simple but effective and elegant visualization based on the following requirements:
|
| 43 |
{placeholder_text}
|
| 44 |
|
| 45 |
+
// Critical Requirements
|
| 46 |
+
- Use ONLY SVG elements or Plotly.js for compatibility with PDF rendering
|
| 47 |
+
- White background for all elements (#ffffff)
|
| 48 |
+
- No external dependencies except CDN-hosted Plotly/D3
|
| 49 |
+
- Include explicit width/height attributes in SVG tags
|
| 50 |
+
- Font size minimum 12px for readability in PDF
|
| 51 |
+
- Include all required <script> tags for libraries
|
| 52 |
+
- Add 'xmlns="http://www.w3.org/2000/svg"' attribute to SVG tags
|
| 53 |
+
- Use high-contrast colors (no dark backgrounds)
|
| 54 |
+
- Include descriptive <title> elements for accessibility
|
| 55 |
+
|
| 56 |
+
// Important
|
| 57 |
+
- Make the visuals content rich, there's no point having a visual if its content is pointless.
|
| 58 |
+
- It has to convey some relevant insights.
|
| 59 |
+
- Take a deep breath, think step by step and think it well.
|
| 60 |
+
|
| 61 |
+
// Preferred Patterns
|
| 62 |
+
<svg width="500" height="500" xmlns="http://www.w3.org/2000/svg">
|
| 63 |
+
<!-- SVG elements here -->
|
| 64 |
+
</svg>
|
| 65 |
+
|
| 66 |
+
OR
|
| 67 |
+
|
| 68 |
+
<div id="chart">
|
| 69 |
+
<!-- Plotly chart -->
|
| 70 |
+
</div>
|
| 71 |
+
<script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
|
| 72 |
+
""")
|
| 73 |
)
|
| 74 |
result = call_visual_llm(prompt)
|
| 75 |
+
logging.info(f"The code produced for this visual placeholder:\n{placeholder_text}\n\n {result}\n\n")
|
| 76 |
return result
|
| 77 |
|
| 78 |
def replace_visual_placeholders(report_html: str, context: str, initial_query: str, crumbs: str) -> str:
|
| 79 |
+
pattern = r"\[\[Visual Placeholder (\d+):(.*?)\]\]" # Capture placeholder number
|
| 80 |
+
replacements = []
|
| 81 |
|
| 82 |
def placeholder_replacer(match):
|
| 83 |
+
placeholder_num = match.group(1)
|
| 84 |
+
instructions = match.group(2).strip()
|
| 85 |
+
logging.info(f"Generating visual {placeholder_num}")
|
| 86 |
|
| 87 |
+
try:
|
| 88 |
+
visual_html = generate_visual_snippet(instructions, context, initial_query, crumbs)
|
| 89 |
+
# Add error boundary and logging
|
| 90 |
+
return f'<!-- Visual {placeholder_num} Start -->\n{visual_html}\n<!-- Visual {placeholder_num} End -->'
|
| 91 |
+
except Exception as e:
|
| 92 |
+
logging.error(f"Visual {placeholder_num} failed: {str(e)}")
|
| 93 |
+
return f'<!-- ERROR GENERATING VISUAL {placeholder_num} -->'
|
| 94 |
|
| 95 |
+
return re.sub(pattern, placeholder_replacer, report_html, flags=re.DOTALL)
|
|
|
|
|
|
|
| 96 |
|
| 97 |
def get_random_header():
|
| 98 |
headers = [
|
|
|
|
| 624 |
logging.info(f"refine_query: Refined query: {refined}")
|
| 625 |
return refined
|
| 626 |
|
| 627 |
+
def validate_visual_html(html: str) -> bool:
|
| 628 |
+
"""Basic sanity check for generated visuals"""
|
| 629 |
+
checks = [
|
| 630 |
+
("<svg" in html) or ("plotly" in html.lower()),
|
| 631 |
+
"background" not in html.lower() or "#fff" in html.lower(),
|
| 632 |
+
not re.search(r"color\s*:\s*#000000", html, re.I)
|
| 633 |
+
]
|
| 634 |
+
return all(checks)
|
| 635 |
+
|
| 636 |
class ReportGenerator:
|
| 637 |
def __init__(self):
|
| 638 |
pass
|
|
|
|
| 692 |
logging.info("ReportGenerator: HTML report generated successfully.")
|
| 693 |
return full_html
|
| 694 |
|
| 695 |
+
def fallback_pdf_generation(self, html_content: str) -> bytes:
|
| 696 |
+
"""Convert HTML to PDF using screenshot fallback"""
|
| 697 |
+
from selenium import webdriver
|
| 698 |
+
from selenium.webdriver.chrome.options import Options
|
| 699 |
+
|
| 700 |
+
options = Options()
|
| 701 |
+
options.add_argument("--headless")
|
| 702 |
+
options.add_argument("--disable-gpu")
|
| 703 |
+
options.add_argument("--no-sandbox")
|
| 704 |
+
options.add_argument("--window-size=1920,1080")
|
| 705 |
+
|
| 706 |
+
driver = webdriver.Chrome(options=options)
|
| 707 |
+
try:
|
| 708 |
+
driver.get(f"data:text/html;charset=utf-8,{html_content}")
|
| 709 |
+
time.sleep(2) # Allow charts to render
|
| 710 |
+
return driver.get_screenshot_as_png()
|
| 711 |
+
finally:
|
| 712 |
+
driver.quit()
|
| 713 |
+
|
| 714 |
def generate_report_pdf(self, solution_content: str, metadata: dict = None) -> bytes:
|
| 715 |
+
# Convert dynamic JS charts to static images
|
|
|
|
|
|
|
|
|
|
| 716 |
html_report = self.generate_report_html(solution_content, metadata)
|
| 717 |
+
|
| 718 |
+
# Add PDF-specific CSS
|
| 719 |
+
html_report = html_report.replace("<style>", """<style>
|
| 720 |
+
@media print {
|
| 721 |
+
.visual-container { page-break-inside: avoid; }
|
| 722 |
+
svg { max-width: 100% !important; height: auto !important; }
|
| 723 |
+
}
|
| 724 |
+
""")
|
| 725 |
+
|
| 726 |
+
# Convert to PDF
|
| 727 |
pdf_buffer = io.BytesIO()
|
| 728 |
pisa_status = pisa.CreatePDF(html_report, dest=pdf_buffer)
|
| 729 |
+
|
| 730 |
+
# Fallback for JS charts
|
| 731 |
if pisa_status.err:
|
| 732 |
+
logging.warning("PDF conversion issues detected - attempting image fallback")
|
| 733 |
+
return self.fallback_pdf_generation(html_report)
|
| 734 |
+
|
| 735 |
return pdf_buffer.getvalue()
|
| 736 |
|
| 737 |
def handle_generate_report(query_name: str, user_name: str, final_report: str):
|
|
|
|
| 926 |
|
| 927 |
alignment_assessment = assess_report_alignment(final_report, initial_query, followup_clarifications)
|
| 928 |
final_report += f"""
|
|
|
|
|
|
|
| 929 |
|
| 930 |
<p><b>Report alignment assessment:</b>
|
| 931 |
{alignment_assessment}</p>"""
|