Guiyom commited on
Commit
074c65c
·
verified ·
1 Parent(s): 734557d

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +88 -46
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("html").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 (only to call relevant libraries).
42
  The code should display a simple but effective and elegant visualization based on the following requirements:
43
  {placeholder_text}
44
 
45
- // Reference
46
- The visual is expected to be integrated within a report generated for the user, it should make use of any relevant information from:
47
- - the initial user query:
48
- {initial_query}
49
- - the overall context
50
- {context}
51
- - some knowledge material gathered from search engines
52
- {crumbs}
53
-
54
- // Requirements
55
- - the dimensions should be less than 500px height and 500px width (it should be printable once the report is converted to pdf)
56
- - use a font no larger than 10, with bold and italic if needed
57
- - if for a specific shape the background is dark, the text should be white (and vice versa if the background is clear)
58
- - Use HTML5 elements if necessary
59
- - Display either:
60
- o chart (histogram, curve) with the proper call to a js library (ex: d3.js or plotly)
61
- o a diagram (in the style of a mindmap, or five forces, or a flow chart)
62
- - keep it simple but effective to convey the message
63
-
64
- // IMPORTTANT
65
- - output only the code
66
- - no extra explanation
67
- - no code fences
68
- - do not add <html> </html> or <!DOCTYPE html>, the snippet will be integrated in a html code body part at a pre-defined location
69
- """
 
 
 
70
  )
71
  result = call_visual_llm(prompt)
72
- logging.info(f"The code produced for this visual placeholder:\n{placeholder_text}\n\n\n {result}\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+:(.*?)\]\]" # Regex to match placeholders
 
77
 
78
  def placeholder_replacer(match):
79
- placeholder_instructions = match.group(1).strip() # Extract and strip placeholder instructions
80
- logging.info(f"Generating visual for placeholder: {placeholder_instructions}")
 
81
 
82
- # Call the visual generation function:
83
- visual_html = generate_visual_snippet(placeholder_instructions, context, initial_query, crumbs)
84
- return visual_html
 
 
 
 
85
 
86
- # Replace all matches in the HTML
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
- # Convert the HTML to PDF using xhtml2pdf (pisa)
 
 
 
 
 
 
 
 
 
686
  pdf_buffer = io.BytesIO()
687
  pisa_status = pisa.CreatePDF(html_report, dest=pdf_buffer)
 
 
688
  if pisa_status.err:
689
- raise Exception("Error converting HTML to PDF")
690
- logging.info("ReportGenerator: PDF report generated successfully from HTML.")
 
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>"""