Add FastMCP tool for web scrapping.
Add logging info for tools calling
This commit is contained in:
+5
-2
@@ -1,4 +1,5 @@
|
||||
import logging
|
||||
import sys
|
||||
from xmlrpc.client import boolean
|
||||
|
||||
|
||||
@@ -12,13 +13,15 @@ def simple_logger(log_relative_path = str, printToConsole = boolean, logger_name
|
||||
|
||||
formatter = logging.Formatter(fmt="%(asctime)s [%(levelname)s] %(message)s",
|
||||
datefmt="%Y-%m-%d %H:%M:%S")
|
||||
file_handler = logging.FileHandler(log_relative_path)
|
||||
file_handler = logging.FileHandler(log_relative_path, encoding="utf-8")
|
||||
file_handler.setFormatter(formatter)
|
||||
file_handler.setLevel(logging.INFO)
|
||||
logger.addHandler(file_handler)
|
||||
|
||||
if printToConsole:
|
||||
console_handler = logging.StreamHandler()
|
||||
# 2. Force StreamHandler to use sys.stdout with utf-8 encoding
|
||||
sys.stdout.reconfigure(encoding='utf-8')
|
||||
console_handler = logging.StreamHandler(sys.stdout)
|
||||
console_handler.setFormatter(formatter)
|
||||
console_handler.setLevel(logging.INFO)
|
||||
logger.addHandler(console_handler)
|
||||
|
||||
+245
-31
@@ -1,40 +1,168 @@
|
||||
# html_template = """
|
||||
# <!DOCTYPE html>
|
||||
# <html>
|
||||
# <head>
|
||||
# <meta charset="utf-8">
|
||||
# <style>
|
||||
# /* Existing styles... */
|
||||
|
||||
# /* Styles for Links */
|
||||
# a {{
|
||||
# color: #0000ff; /* Sets the link color to blue */
|
||||
# text-decoration: underline; /* Ensures the underline is visible */
|
||||
# }}
|
||||
# a:visited {{
|
||||
# color: #0000ff; /* Keeps the link blue even after it is clicked */
|
||||
# }}
|
||||
|
||||
# /* Styles for clean Table Lines */
|
||||
# table {{
|
||||
# width: 100%;
|
||||
# border-collapse: collapse; /* Merges adjacent cell borders into a single line */
|
||||
# margin: 20px 0;
|
||||
# font-size: 11pt;
|
||||
# }}
|
||||
# th, td {{
|
||||
# border: 1px solid #bdc3c7; /* Draws the actual grid lines */
|
||||
# padding: 10px;
|
||||
# text-align: left;
|
||||
# }}
|
||||
# th {{
|
||||
# background-color: #f2f4f4; /* Optional: adds a neat background header color */
|
||||
# font-weight: bold;
|
||||
# color: #2c3e50;
|
||||
# }}
|
||||
# tr:nth-child(even) {{
|
||||
# background-color: #f9f9f9; /* Optional: adds zebra striping to rows */
|
||||
# }}
|
||||
|
||||
# /* Table of Contents Styles */
|
||||
# .toc-list {{
|
||||
# list-style-type: decimal;
|
||||
# padding-left: 20px;
|
||||
# margin: 10px 0;
|
||||
# }}
|
||||
# .toc-list li {{
|
||||
# margin-bottom: 6px;
|
||||
# font-size: 11pt;
|
||||
# }}
|
||||
# /* Styles the nested sub-sections */
|
||||
# .toc-list ul {{
|
||||
# list-style-type: none; /* Removes numbers/bullets from sub-items */
|
||||
# padding-left: 20px; /* Creates the indent */
|
||||
# margin: 4px 0;
|
||||
# }}
|
||||
# .toc-list ul li {{
|
||||
# position: relative;
|
||||
# font-size: 10.5pt;
|
||||
# color: #555;
|
||||
# }}
|
||||
# /* Adds a clean dash prefix (-) to the sub-items */
|
||||
# .toc-list ul li::before {{
|
||||
# content: "- ";
|
||||
# position: absolute;
|
||||
# left: -12px;
|
||||
# }}
|
||||
# </style>
|
||||
# </head>
|
||||
# <body>
|
||||
# {content}
|
||||
# </body>
|
||||
# </html>
|
||||
# """
|
||||
|
||||
html_template = """
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
/* Existing styles... */
|
||||
|
||||
/* Styles for Links */
|
||||
a {{
|
||||
color: #0000ff; /* Sets the link color to blue */
|
||||
text-decoration: underline; /* Ensures the underline is visible */
|
||||
}}
|
||||
a:visited {{
|
||||
color: #0000ff; /* Keeps the link blue even after it is clicked */
|
||||
/* --- WeasyPrint Page Parameters --- */
|
||||
@page {{
|
||||
size: A4;
|
||||
margin: 20mm;
|
||||
@bottom-right {{
|
||||
content: counter(page) " / " counter(pages);
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 9pt;
|
||||
color: #7f8c8d;
|
||||
}}
|
||||
}}
|
||||
|
||||
/* Styles for clean Table Lines */
|
||||
table {{
|
||||
width: 100%;
|
||||
border-collapse: collapse; /* Merges adjacent cell borders into a single line */
|
||||
margin: 20px 0;
|
||||
body {{
|
||||
font-family: Arial, sans-serif;
|
||||
color: #2c3e50;
|
||||
line-height: 1.6;
|
||||
font-size: 11pt;
|
||||
}}
|
||||
th, td {{
|
||||
border: 1px solid #bdc3c7; /* Draws the actual grid lines */
|
||||
padding: 10px;
|
||||
text-align: left;
|
||||
|
||||
/* --- Table of Contents Component --- */
|
||||
.toc-box {{
|
||||
margin: 25px 0;
|
||||
padding: 20px;
|
||||
background-color: #fcfcfc;
|
||||
border: 1px solid #eaeaea;
|
||||
border-radius: 4px;
|
||||
}}
|
||||
th {{
|
||||
background-color: #f2f4f4; /* Optional: adds a neat background header color */
|
||||
|
||||
/* Direct Parents (1., 2., 3.) */
|
||||
.toc-box > ol {{
|
||||
list-style-type: decimal;
|
||||
padding-left: 20px;
|
||||
margin: 0;
|
||||
}}
|
||||
|
||||
.toc-box > ol > li {{
|
||||
font-size: 12pt;
|
||||
font-weight: bold;
|
||||
margin-top: 12px;
|
||||
color: #2c3e50;
|
||||
}}
|
||||
tr:nth-child(even) {{
|
||||
background-color: #f9f9f9; /* Optional: adds zebra striping to rows */
|
||||
|
||||
/* Targets sub-items that Python and Markdown nested inside the <li> tag */
|
||||
.toc-box ol li ul {{
|
||||
list-style-type: none; /* Destroys standard browser bullet points */
|
||||
padding-left: 20px; /* Indents sub-items perfectly to the right */
|
||||
margin: 6px 0 0 0;
|
||||
}}
|
||||
|
||||
.toc-box ol li ul li {{
|
||||
font-size: 11pt;
|
||||
font-weight: normal; /* Removes parent bold styles from sub-items */
|
||||
color: #555555;
|
||||
margin-bottom: 5px;
|
||||
position: relative;
|
||||
}}
|
||||
|
||||
/* Injects a clean layout hyphen prefix natively via CSS */
|
||||
.toc-box ol li ul li::before {{
|
||||
content: "- ";
|
||||
font-weight: bold;
|
||||
color: #7f8c8d;
|
||||
}}
|
||||
|
||||
/* --- Global Structural Typography --- */
|
||||
h1, h2, h3, h4, h5, h6 {{
|
||||
color: #2c3e50;
|
||||
font-weight: bold;
|
||||
page-break-after: avoid;
|
||||
break-after: avoid;
|
||||
}}
|
||||
|
||||
table {{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 24px 0;
|
||||
font-size: 10.5pt;
|
||||
page-break-inside: auto;
|
||||
break-inside: auto;
|
||||
}}
|
||||
tr {{ page-break-inside: avoid; break-inside: avoid; }}
|
||||
th, td {{ border: 1px solid #bdc3c7; padding: 10px; text-align: left; }}
|
||||
th {{ background-color: #f2f4f4; font-weight: bold; }}
|
||||
|
||||
a {{ color: #2980b9; text-decoration: none; }}
|
||||
.page-break {{ page-break-before: always; }}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
@@ -43,18 +171,104 @@ html_template = """
|
||||
</html>
|
||||
"""
|
||||
|
||||
import re
|
||||
import markdown
|
||||
from html.parser import HTMLParser
|
||||
from weasyprint import HTML
|
||||
|
||||
def export_to_pdf(inputText, filename):
|
||||
class TOCHtmlRestructurer(HTMLParser):
|
||||
def __init__(self):
|
||||
super().__init__()
|
||||
self.output = []
|
||||
self.level_stack = [] # Tracks nesting hierarchy
|
||||
self.parent_count = 0
|
||||
self.child_count = 0
|
||||
self.capture_text = False
|
||||
self.current_item_text = ""
|
||||
self.current_href = None # Tracks anchor references dynamically
|
||||
|
||||
# Step 1: Convert Markdown components to HTML chunks.
|
||||
# The 'tables' and 'fenced_code' extensions keep standard LLM formatting neat.
|
||||
def handle_starttag(self, tag, attrs):
|
||||
if tag in ['ol', 'ul']:
|
||||
if not self.level_stack:
|
||||
self.level_stack.append('parent')
|
||||
else:
|
||||
self.level_stack.append('child')
|
||||
elif tag == 'li':
|
||||
self.capture_text = True
|
||||
self.current_item_text = ""
|
||||
self.current_href = None
|
||||
elif tag == 'a' and self.capture_text:
|
||||
# Capture the link anchor destination safely
|
||||
attrs_dict = dict(attrs)
|
||||
self.current_href = attrs_dict.get('href')
|
||||
|
||||
def handle_endtag(self, tag):
|
||||
if tag in ['ol', 'ul']:
|
||||
if self.level_stack:
|
||||
self.level_stack.pop()
|
||||
elif tag == 'li':
|
||||
self.capture_text = False
|
||||
self.process_accumulated_item()
|
||||
|
||||
def handle_data(self, data):
|
||||
if self.capture_text:
|
||||
self.current_item_text += data
|
||||
|
||||
def process_accumulated_item(self):
|
||||
# 1. Clean out stray prefix digits and list flags safely
|
||||
clean = self.current_item_text.strip()
|
||||
clean = re.sub(r'^([\d\.\s\-•]*\d+\.\d+|[\d\.\s\-•]*\d+\.)\s*', '', clean)
|
||||
clean = re.sub(r'^\d+\s+(?=[A-Za-z])', '', clean)
|
||||
clean = re.sub(r'^[\s\-•]*', '', clean).strip()
|
||||
|
||||
if not clean:
|
||||
return
|
||||
|
||||
# 2. Check current depth level stack state
|
||||
current_state = self.level_stack[-1] if self.level_stack else 'parent'
|
||||
|
||||
# 3. Rebuild the text as a clickable link if an anchor was present
|
||||
if self.current_href:
|
||||
display_text = f'<a href="{self.current_href}">{clean}</a>'
|
||||
else:
|
||||
display_text = clean
|
||||
|
||||
# 4. Generate structured HTML outputs with calculated taxonomy counters
|
||||
if current_state == 'parent':
|
||||
self.parent_count += 1
|
||||
self.child_count = 0
|
||||
self.output.append(f'<div class="toc-parent">{self.parent_count}. {display_text}</div>')
|
||||
else:
|
||||
self.child_count += 1
|
||||
self.output.append(f'<div class="toc-child">{self.parent_count}.{self.child_count} {display_text}</div>')
|
||||
|
||||
def extract_and_fix_toc_blocks(html_content):
|
||||
"""
|
||||
Finds the compiled HTML lists block, intercepts it, clears
|
||||
numbering artifacts structurally via HTML parsing tree mechanics.
|
||||
"""
|
||||
match = re.search(r'(<(?:ol|ul)>[\s\S]*?</(?:ol|ul)>)', html_content)
|
||||
if not match:
|
||||
return html_content
|
||||
|
||||
raw_toc_html = match.group(1)
|
||||
|
||||
parser = TOCHtmlRestructurer()
|
||||
parser.feed(raw_toc_html)
|
||||
|
||||
new_toc_html = '<div class="toc-box">\n<h2>Table of Contents</h2>\n'
|
||||
new_toc_html += '\n'.join(parser.output)
|
||||
new_toc_html += '\n</div>'
|
||||
|
||||
return html_content.replace(raw_toc_html, new_toc_html, 1)
|
||||
|
||||
def export_to_pdf(inputText, filename):
|
||||
"""
|
||||
Processes plain dynamic markdown, captures structural compilation output,
|
||||
restructures TOC nodes downstream safely, and outputs a high-fidelity PDF.
|
||||
"""
|
||||
html_content = markdown.markdown(inputText, extensions=['tables', 'fenced_code'])
|
||||
final_body_content = extract_and_fix_toc_blocks(html_content)
|
||||
|
||||
# Step 2: Inject the content into our CSS-styled HTML boilerplate template
|
||||
final_html = html_template.format(content=html_content)
|
||||
|
||||
# Step 3: Render directly to a high-fidelity PDF file
|
||||
HTML(string=final_html).write_pdf(filename)
|
||||
#print(f"Successfully generated styled PDF at: {output_pdf_path}")
|
||||
final_html = html_template.format(content=final_body_content)
|
||||
HTML(string=final_html).write_pdf(filename)
|
||||
@@ -0,0 +1,133 @@
|
||||
import markdown
|
||||
from weasyprint import HTML
|
||||
|
||||
# ==========================================
|
||||
# REUSABLE HTML & CSS LAYOUT TEMPLATE
|
||||
# ==========================================
|
||||
PDF_REPORT_TEMPLATE = """<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<style>
|
||||
@page {{
|
||||
size: A4;
|
||||
margin: 20mm;
|
||||
@bottom-right {{
|
||||
content: counter(page);
|
||||
font-family: Arial, sans-serif;
|
||||
font-size: 9pt;
|
||||
}}
|
||||
}}
|
||||
|
||||
body {{
|
||||
font-family: Arial, sans-serif;
|
||||
color: #333333;
|
||||
line-height: 1.6;
|
||||
font-size: 11pt;
|
||||
}}
|
||||
|
||||
h1 {{ font-size: 18pt; margin-top: 24pt; border-bottom: 1px solid #ddd; padding-bottom: 6px; }}
|
||||
h2 {{ font-size: 14pt; margin-top: 18pt; color: #2c3e50; }}
|
||||
h3 {{ font-size: 12pt; margin-top: 14pt; color: #7f8c8d; }}
|
||||
|
||||
/* --- Layout Tables --- */
|
||||
table {{
|
||||
width: 100%;
|
||||
border-collapse: collapse;
|
||||
margin: 15pt 0;
|
||||
}}
|
||||
th, td {{
|
||||
border: 1px solid #dddddd;
|
||||
padding: 8px;
|
||||
text-align: left;
|
||||
}}
|
||||
th {{ background-color: #f8f9fa; font-weight: bold; }}
|
||||
tr:nth-child(even) {{ background-color: #fdfdfd; }}
|
||||
|
||||
/* --- Links --- */
|
||||
a {{ color: #3498db; text-decoration: none; }}
|
||||
a:hover {{ text-decoration: underline; }}
|
||||
|
||||
/* ==========================================
|
||||
DYNAMIC PAGED MEDIA TOC LAYOUT
|
||||
========================================== */
|
||||
.toc-wrapper {{
|
||||
margin: 20pt 0;
|
||||
padding: 15pt;
|
||||
background: #fdfdfd;
|
||||
border: 1px solid #eaeaea;
|
||||
border-radius: 5px;
|
||||
page-break-after: always;
|
||||
}}
|
||||
|
||||
/* Clear default markdown padding/bullets on root list element */
|
||||
.toc-wrapper > ul {{
|
||||
list-style-type: none;
|
||||
padding-left: 0;
|
||||
}}
|
||||
|
||||
/* Main Level 1 entries (bolded for contrast) */
|
||||
.toc-wrapper > ul > li {{
|
||||
font-weight: bold;
|
||||
margin-top: 12px;
|
||||
margin-bottom: 6px;
|
||||
}}
|
||||
|
||||
/* Indent Subsections (Level 2 nested lists) */
|
||||
.toc-wrapper li ul {{
|
||||
list-style-type: none;
|
||||
padding-left: 25px;
|
||||
margin-top: 4px;
|
||||
margin-bottom: 4px;
|
||||
}}
|
||||
|
||||
/* Ensure subsection text lines are regular weight */
|
||||
.toc-wrapper li ul li {{
|
||||
font-weight: normal;
|
||||
margin-bottom: 6px;
|
||||
}}
|
||||
|
||||
.toc-wrapper li {{
|
||||
position: relative;
|
||||
display: block;
|
||||
}}
|
||||
|
||||
/* Pulls the target page number of the markdown heading reference link */
|
||||
.toc-wrapper a::after {{
|
||||
content: target-counter(attr(href), page);
|
||||
float: right;
|
||||
color: #7f8c8d;
|
||||
font-weight: normal;
|
||||
}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="content">
|
||||
{html_body}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
"""
|
||||
|
||||
def export_to_pdf(llm_response: str, output_filename: str = "output.pdf") -> None:
|
||||
"""Converts structured LLM response with a fully clickable, indented manual TOC into a clean PDF."""
|
||||
|
||||
# 1. Standard markdown conversion with 'toc' extension enabled.
|
||||
# This automatically adds unique ID anchors to your <h1> and <h2> elements
|
||||
# so that clicking the TOC links will correctly jump down to that section.
|
||||
html_body = markdown.markdown(
|
||||
llm_response,
|
||||
extensions=['fenced_code', 'tables', 'toc']
|
||||
)
|
||||
|
||||
# 2. Inject a styling hook class wrapper around your Table of Contents list block
|
||||
html_body = html_body.replace('<h3>Table of Contents</h3>', '<h3>Table of Contents</h3><div class="toc-wrapper">')
|
||||
|
||||
# Close the div wrapper cleanly right before the next main topic heading begins
|
||||
html_body = html_body.replace('<h1>1.', '</div><h1>1.')
|
||||
|
||||
# 3. Format the final output document string structure
|
||||
final_html_content = PDF_REPORT_TEMPLATE.format(html_body=html_body)
|
||||
|
||||
# 4. Generate the clickable PDF file
|
||||
HTML(string=final_html_content).write_pdf(output_filename)
|
||||
Reference in New Issue
Block a user