Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -23,7 +23,6 @@ import requests
|
|
| 23 |
from PIL import Image as PilImage
|
| 24 |
import io
|
| 25 |
import tempfile
|
| 26 |
-
import time
|
| 27 |
from datetime import datetime
|
| 28 |
|
| 29 |
# Set up logging
|
|
@@ -33,9 +32,6 @@ logging.basicConfig(
|
|
| 33 |
)
|
| 34 |
logger = logging.getLogger(__name__)
|
| 35 |
|
| 36 |
-
# Get Gemini API key from Hugging Face Space secrets
|
| 37 |
-
GEMINI_API_KEY = os.environ.get("GEMINI_API_KEY", "")
|
| 38 |
-
|
| 39 |
class MarkdownToPDFConverter:
|
| 40 |
"""
|
| 41 |
Class to convert Markdown content to PDF using ReportLab.
|
|
@@ -49,8 +45,7 @@ class MarkdownToPDFConverter:
|
|
| 49 |
base_font_size: int = 10,
|
| 50 |
heading_scale: Dict[int, float] = None,
|
| 51 |
include_toc: bool = True,
|
| 52 |
-
code_style: str = "github"
|
| 53 |
-
color_theme: str = "Default"
|
| 54 |
):
|
| 55 |
"""
|
| 56 |
Initialize the converter with configuration options.
|
|
@@ -64,7 +59,6 @@ class MarkdownToPDFConverter:
|
|
| 64 |
heading_scale: Dictionary of heading levels to font size multipliers
|
| 65 |
include_toc: Whether to include a table of contents
|
| 66 |
code_style: Style to use for code blocks
|
| 67 |
-
color_theme: Color theme for the PDF
|
| 68 |
"""
|
| 69 |
self.output_path = output_path
|
| 70 |
self.page_size = A4 if page_size.upper() == "A4" else letter
|
|
@@ -81,7 +75,6 @@ class MarkdownToPDFConverter:
|
|
| 81 |
}
|
| 82 |
self.include_toc = include_toc
|
| 83 |
self.code_style = code_style
|
| 84 |
-
self.color_theme = color_theme
|
| 85 |
|
| 86 |
# Initialize styles
|
| 87 |
self.styles = getSampleStyleSheet()
|
|
@@ -93,18 +86,6 @@ class MarkdownToPDFConverter:
|
|
| 93 |
|
| 94 |
def _setup_styles(self) -> None:
|
| 95 |
"""Set up custom paragraph styles for the document."""
|
| 96 |
-
# Color theme configuration
|
| 97 |
-
theme_colors = {
|
| 98 |
-
"Default": {"headings": HexColor('#000000'), "code_bg": HexColor('#EEEEEE')},
|
| 99 |
-
"Modern": {"headings": HexColor('#2962FF'), "code_bg": HexColor('#F5F5F5')},
|
| 100 |
-
"Professional": {"headings": HexColor('#1565C0'), "code_bg": HexColor('#F8F9FA')},
|
| 101 |
-
"Creative": {"headings": HexColor('#6200EA'), "code_bg": HexColor('#F3E5F5')},
|
| 102 |
-
"Elegant": {"headings": HexColor('#00695C'), "code_bg": HexColor('#E0F2F1')}
|
| 103 |
-
}
|
| 104 |
-
|
| 105 |
-
# Use selected theme or default
|
| 106 |
-
theme = theme_colors.get(self.color_theme, theme_colors["Default"])
|
| 107 |
-
|
| 108 |
# Modify existing Normal style
|
| 109 |
self.styles['Normal'].fontName = self.font_name
|
| 110 |
self.styles['Normal'].fontSize = self.base_font_size
|
|
@@ -125,7 +106,6 @@ class MarkdownToPDFConverter:
|
|
| 125 |
self.styles[heading_name].leading = int(self.base_font_size * size_multiplier * 1.2)
|
| 126 |
self.styles[heading_name].spaceAfter = self.base_font_size
|
| 127 |
self.styles[heading_name].spaceBefore = self.base_font_size * (1 + (0.2 * (7 - level)))
|
| 128 |
-
self.styles[heading_name].textColor = theme["headings"]
|
| 129 |
else:
|
| 130 |
# Create new style
|
| 131 |
self.styles.add(
|
|
@@ -137,7 +117,6 @@ class MarkdownToPDFConverter:
|
|
| 137 |
leading=int(self.base_font_size * size_multiplier * 1.2),
|
| 138 |
spaceAfter=self.base_font_size,
|
| 139 |
spaceBefore=self.base_font_size * (1 + (0.2 * (7 - level))),
|
| 140 |
-
textColor=theme["headings"]
|
| 141 |
)
|
| 142 |
)
|
| 143 |
|
|
@@ -151,26 +130,12 @@ class MarkdownToPDFConverter:
|
|
| 151 |
spaceAfter=self.base_font_size,
|
| 152 |
spaceBefore=self.base_font_size,
|
| 153 |
leftIndent=self.base_font_size,
|
| 154 |
-
backgroundColor=
|
| 155 |
borderWidth=0,
|
| 156 |
borderPadding=self.base_font_size * 0.5,
|
| 157 |
)
|
| 158 |
)
|
| 159 |
|
| 160 |
-
# Blockquote style
|
| 161 |
-
self.styles.add(
|
| 162 |
-
ParagraphStyle(
|
| 163 |
-
name='Blockquote',
|
| 164 |
-
parent=self.styles['Normal'],
|
| 165 |
-
leftIndent=self.base_font_size * 2,
|
| 166 |
-
rightIndent=self.base_font_size,
|
| 167 |
-
fontStyle='italic',
|
| 168 |
-
spaceAfter=self.base_font_size,
|
| 169 |
-
spaceBefore=self.base_font_size,
|
| 170 |
-
textColor=HexColor('#555555'),
|
| 171 |
-
)
|
| 172 |
-
)
|
| 173 |
-
|
| 174 |
# List item style
|
| 175 |
self.styles.add(
|
| 176 |
ParagraphStyle(
|
|
@@ -188,7 +153,6 @@ class MarkdownToPDFConverter:
|
|
| 188 |
parent=self.styles['Heading1'],
|
| 189 |
fontSize=int(self.base_font_size * 1.5),
|
| 190 |
spaceAfter=self.base_font_size * 1.5,
|
| 191 |
-
textColor=theme["headings"],
|
| 192 |
)
|
| 193 |
)
|
| 194 |
|
|
@@ -278,8 +242,7 @@ class MarkdownToPDFConverter:
|
|
| 278 |
base_font_size=self.base_font_size,
|
| 279 |
heading_scale=self.heading_scale,
|
| 280 |
include_toc=separate_toc,
|
| 281 |
-
code_style=self.code_style
|
| 282 |
-
color_theme=self.color_theme
|
| 283 |
)
|
| 284 |
converter.convert_file(file_path)
|
| 285 |
|
|
@@ -355,19 +318,6 @@ class MarkdownToPDFConverter:
|
|
| 355 |
Preformatted(code, self.styles['CodeBlock'])
|
| 356 |
)
|
| 357 |
|
| 358 |
-
elif element.name == 'blockquote':
|
| 359 |
-
# Process blockquote content
|
| 360 |
-
blockquote_text = ""
|
| 361 |
-
for child in element.children:
|
| 362 |
-
if hasattr(child, 'name') and child.name:
|
| 363 |
-
blockquote_text += self._process_inline_elements(child)
|
| 364 |
-
else:
|
| 365 |
-
blockquote_text += str(child)
|
| 366 |
-
|
| 367 |
-
self.elements.append(
|
| 368 |
-
Paragraph(blockquote_text, self.styles['Blockquote'])
|
| 369 |
-
)
|
| 370 |
-
|
| 371 |
elif element.name == 'img':
|
| 372 |
src = element.get('src', '')
|
| 373 |
alt = element.get('alt', 'Image')
|
|
@@ -428,7 +378,7 @@ class MarkdownToPDFConverter:
|
|
| 428 |
|
| 429 |
elif element.name == 'ul' or element.name == 'ol':
|
| 430 |
list_items = []
|
| 431 |
-
|
| 432 |
|
| 433 |
for item in element.find_all('li', recursive=False):
|
| 434 |
text = self._process_inline_elements(item)
|
|
@@ -439,21 +389,12 @@ class MarkdownToPDFConverter:
|
|
| 439 |
)
|
| 440 |
)
|
| 441 |
|
| 442 |
-
if is_ordered:
|
| 443 |
-
self.elements.append(
|
| 444 |
-
ListFlowable(
|
| 445 |
-
list_items,
|
| 446 |
-
bulletType='1',
|
| 447 |
-
start=1,
|
| 448 |
-
bulletFormat='%s.'
|
| 449 |
-
)
|
| 450 |
-
)
|
| 451 |
-
else:
|
| 452 |
self.elements.append(
|
| 453 |
ListFlowable(
|
| 454 |
list_items,
|
| 455 |
-
bulletType=
|
| 456 |
-
|
|
|
|
| 457 |
)
|
| 458 |
)
|
| 459 |
|
|
@@ -651,7 +592,7 @@ class MarkdownToPDFAgent:
|
|
| 651 |
try:
|
| 652 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
| 653 |
|
| 654 |
-
api_key = api_key or os.getenv("
|
| 655 |
if not api_key:
|
| 656 |
logger.warning("No Google API key provided. Agent will run without LLM enhancement.")
|
| 657 |
return False
|
|
@@ -753,8 +694,7 @@ class MarkdownToPDFAgent:
|
|
| 753 |
return '\n'.join(result_lines)
|
| 754 |
|
| 755 |
def process_file(self, input_path: str, output_path: str = None, enhance: bool = False,
|
| 756 |
-
enhancement_instructions: str = None, page_size: str = "A4"
|
| 757 |
-
color_theme: str = "Default") -> str:
|
| 758 |
"""
|
| 759 |
Process a single markdown file and convert it to PDF.
|
| 760 |
|
|
@@ -764,7 +704,6 @@ class MarkdownToPDFAgent:
|
|
| 764 |
enhance: Whether to enhance the content with LLM
|
| 765 |
enhancement_instructions: Specific instructions for enhancement
|
| 766 |
page_size: Page size for the PDF ("A4" or "letter")
|
| 767 |
-
color_theme: Color theme for the PDF
|
| 768 |
|
| 769 |
Returns:
|
| 770 |
Path to the generated PDF
|
|
@@ -790,8 +729,7 @@ class MarkdownToPDFAgent:
|
|
| 790 |
# Configure converter
|
| 791 |
self.converter = MarkdownToPDFConverter(
|
| 792 |
output_path=output_path,
|
| 793 |
-
page_size=page_size
|
| 794 |
-
color_theme=color_theme
|
| 795 |
)
|
| 796 |
|
| 797 |
# Convert to PDF
|
|
@@ -803,7 +741,7 @@ class MarkdownToPDFAgent:
|
|
| 803 |
def process_directory(self, input_dir: str, output_dir: str = None, pattern: str = "*.md",
|
| 804 |
enhance: bool = False, merge: bool = False,
|
| 805 |
output_filename: str = "merged_document.pdf",
|
| 806 |
-
page_size: str = "A4"
|
| 807 |
"""
|
| 808 |
Process all markdown files in a directory.
|
| 809 |
|
|
@@ -815,7 +753,6 @@ class MarkdownToPDFAgent:
|
|
| 815 |
merge: Whether to merge all files into a single PDF
|
| 816 |
output_filename: Filename for merged PDF
|
| 817 |
page_size: Page size for the PDF ("A4" or "letter")
|
| 818 |
-
color_theme: Color theme for the PDF
|
| 819 |
|
| 820 |
Returns:
|
| 821 |
List of paths to generated PDFs
|
|
@@ -867,8 +804,7 @@ class MarkdownToPDFAgent:
|
|
| 867 |
output_path = os.path.join(output_dir, output_filename)
|
| 868 |
self.converter = MarkdownToPDFConverter(
|
| 869 |
output_path=output_path,
|
| 870 |
-
page_size=page_size
|
| 871 |
-
color_theme=color_theme
|
| 872 |
)
|
| 873 |
self.converter.convert_content(merged_content)
|
| 874 |
|
|
@@ -878,8 +814,7 @@ class MarkdownToPDFAgent:
|
|
| 878 |
output_path = os.path.join(output_dir, output_filename)
|
| 879 |
self.converter = MarkdownToPDFConverter(
|
| 880 |
output_path=output_path,
|
| 881 |
-
page_size=page_size
|
| 882 |
-
color_theme=color_theme
|
| 883 |
)
|
| 884 |
self.converter.convert_multiple_files(md_files, merge=True)
|
| 885 |
|
|
@@ -896,8 +831,7 @@ class MarkdownToPDFAgent:
|
|
| 896 |
md_file,
|
| 897 |
output_path,
|
| 898 |
enhance=enhance,
|
| 899 |
-
page_size=page_size
|
| 900 |
-
color_theme=color_theme
|
| 901 |
)
|
| 902 |
|
| 903 |
if processed_file:
|
|
@@ -905,159 +839,10 @@ class MarkdownToPDFAgent:
|
|
| 905 |
|
| 906 |
return output_files
|
| 907 |
|
| 908 |
-
# Sample markdown templates
|
| 909 |
-
SAMPLE_TEMPLATES = {
|
| 910 |
-
"report": """# Professional Report
|
| 911 |
-
|
| 912 |
-
## Executive Summary
|
| 913 |
-
|
| 914 |
-
This report presents an analysis of the key findings, methodologies, and recommendations for consideration.
|
| 915 |
-
|
| 916 |
-
## Introduction
|
| 917 |
-
|
| 918 |
-
In this section, we provide context and background information essential for understanding the purpose and scope of this report.
|
| 919 |
-
|
| 920 |
-
## Methodology
|
| 921 |
-
|
| 922 |
-
Our approach included the following steps:
|
| 923 |
-
|
| 924 |
-
1. Data collection from multiple sources
|
| 925 |
-
2. Rigorous analysis using industry-standard techniques
|
| 926 |
-
3. Validation of findings through peer review
|
| 927 |
-
4. Development of actionable recommendations
|
| 928 |
-
|
| 929 |
-
## Key Findings
|
| 930 |
-
|
| 931 |
-
| Area | Status | Impact |
|
| 932 |
-
|------|--------|--------|
|
| 933 |
-
| Operations | Satisfactory | Medium |
|
| 934 |
-
| Finance | Needs Improvement | High |
|
| 935 |
-
| Marketing | Excellent | Medium |
|
| 936 |
-
|
| 937 |
-
## Recommendations
|
| 938 |
-
|
| 939 |
-
- Implement process improvements in the finance department
|
| 940 |
-
- Continue successful marketing strategies
|
| 941 |
-
- Monitor operational metrics quarterly
|
| 942 |
-
|
| 943 |
-
## Conclusion
|
| 944 |
-
|
| 945 |
-
The analysis reveals several opportunities for improvement while highlighting existing strengths.
|
| 946 |
-
|
| 947 |
-
> **Note:** This report should be reviewed by all stakeholders before implementation.
|
| 948 |
-
""",
|
| 949 |
-
|
| 950 |
-
"documentation": """# Product Documentation
|
| 951 |
-
|
| 952 |
-
## Overview
|
| 953 |
-
|
| 954 |
-
This document provides comprehensive information about the product, its features, and usage instructions.
|
| 955 |
-
|
| 956 |
-
## Features
|
| 957 |
-
|
| 958 |
-
- **Fast Processing**: Optimized algorithms ensure quick results
|
| 959 |
-
- **User-Friendly Interface**: Intuitive design for ease of use
|
| 960 |
-
- **Advanced Analytics**: In-depth insights and reporting
|
| 961 |
-
- **Secure Storage**: End-to-end encryption for all data
|
| 962 |
-
|
| 963 |
-
## Installation
|
| 964 |
-
|
| 965 |
-
```bash
|
| 966 |
-
# Install via package manager
|
| 967 |
-
npm install product-name
|
| 968 |
-
|
| 969 |
-
# Initialize with configuration
|
| 970 |
-
product-init --config=standard
|
| 971 |
-
```
|
| 972 |
-
|
| 973 |
-
## Usage Examples
|
| 974 |
-
|
| 975 |
-
Here's a simple example of how to use the main features:
|
| 976 |
-
|
| 977 |
-
```javascript
|
| 978 |
-
import { Product } from 'product-name';
|
| 979 |
-
|
| 980 |
-
// Initialize with settings
|
| 981 |
-
const myProduct = new Product({
|
| 982 |
-
mode: 'advanced',
|
| 983 |
-
logging: true
|
| 984 |
-
});
|
| 985 |
-
|
| 986 |
-
// Process data
|
| 987 |
-
const results = myProduct.analyze(data);
|
| 988 |
-
console.log(results);
|
| 989 |
-
```
|
| 990 |
-
|
| 991 |
-
## Troubleshooting
|
| 992 |
-
|
| 993 |
-
| Issue | Solution |
|
| 994 |
-
|-------|----------|
|
| 995 |
-
| Connection Error | Check network settings and firewall |
|
| 996 |
-
| Performance Issues | Increase memory allocation in config |
|
| 997 |
-
| Data Import Failure | Verify file format compatibility |
|
| 998 |
-
|
| 999 |
-
## Support
|
| 1000 |
-
|
| 1001 |
-
For additional assistance, contact [email protected] or visit our help center.
|
| 1002 |
-
""",
|
| 1003 |
-
|
| 1004 |
-
"article": """# The Future of Technology: Trends to Watch
|
| 1005 |
-
|
| 1006 |
-
## Introduction
|
| 1007 |
-
|
| 1008 |
-
Technology continues to evolve at an unprecedented pace, transforming how we live, work, and interact. This article explores emerging trends that will shape our future.
|
| 1009 |
-
|
| 1010 |
-
## Artificial Intelligence Advancements
|
| 1011 |
-
|
| 1012 |
-
AI systems are becoming increasingly sophisticated, with applications across:
|
| 1013 |
-
|
| 1014 |
-
- **Healthcare**: Diagnostic tools and personalized treatment plans
|
| 1015 |
-
- **Finance**: Algorithmic trading and fraud detection
|
| 1016 |
-
- **Manufacturing**: Predictive maintenance and quality control
|
| 1017 |
-
- **Transportation**: Autonomous vehicles and smart traffic management
|
| 1018 |
-
|
| 1019 |
-
Recent research has shown that AI can now outperform human experts in specific domains while complementing human skills in others.
|
| 1020 |
-
|
| 1021 |
-
## Sustainable Technology
|
| 1022 |
-
|
| 1023 |
-
As environmental concerns mount, sustainable technology is gaining prominence:
|
| 1024 |
-
|
| 1025 |
-
1. Renewable energy systems with improved efficiency
|
| 1026 |
-
2. Eco-friendly materials for hardware manufacturing
|
| 1027 |
-
3. Energy-optimized algorithms and software
|
| 1028 |
-
4. Circular economy approaches to technology lifecycle
|
| 1029 |
-
|
| 1030 |
-
## Decentralized Systems
|
| 1031 |
-
|
| 1032 |
-
Blockchain and related technologies are enabling new decentralized approaches:
|
| 1033 |
-
|
| 1034 |
-
```
|
| 1035 |
-
Traditional Model β Decentralized Alternative
|
| 1036 |
-
Central Database β Distributed Ledger
|
| 1037 |
-
Single Authority β Consensus Mechanism
|
| 1038 |
-
Opaque Processes β Transparent Verification
|
| 1039 |
-
```
|
| 1040 |
-
|
| 1041 |
-
## Conclusion
|
| 1042 |
-
|
| 1043 |
-
These technological trends promise significant benefits but also present challenges that require thoughtful approaches to governance, ethics, and implementation.
|
| 1044 |
-
|
| 1045 |
-
> "The best way to predict the future is to invent it." - Alan Kay
|
| 1046 |
-
"""
|
| 1047 |
-
}
|
| 1048 |
-
|
| 1049 |
# Gradio Interface for Hugging Face
|
| 1050 |
-
def load_sample(
|
| 1051 |
-
"""Load a sample markdown document
|
| 1052 |
-
|
| 1053 |
-
return SAMPLE_TEMPLATES["report"]
|
| 1054 |
-
elif template_choice == "Documentation":
|
| 1055 |
-
return SAMPLE_TEMPLATES["documentation"]
|
| 1056 |
-
elif template_choice == "Article":
|
| 1057 |
-
return SAMPLE_TEMPLATES["article"]
|
| 1058 |
-
else:
|
| 1059 |
-
# Default basic sample
|
| 1060 |
-
return """# Sample Markdown Document
|
| 1061 |
|
| 1062 |
## Introduction
|
| 1063 |
This is a sample markdown document to demonstrate the capabilities of **MarkdownMuse**. You can use this as a starting point for your own documents.
|
|
@@ -1085,8 +870,7 @@ def hello_world():
|
|
| 1085 |
"""
|
| 1086 |
|
| 1087 |
def process_markdown(markdown_text, page_size, font_size, font_name,
|
| 1088 |
-
|
| 1089 |
-
enhancement_instructions=None):
|
| 1090 |
"""
|
| 1091 |
Process markdown text and generate a PDF.
|
| 1092 |
|
|
@@ -1098,7 +882,7 @@ def process_markdown(markdown_text, page_size, font_size, font_name,
|
|
| 1098 |
output_path = temp_file.name
|
| 1099 |
temp_file.close()
|
| 1100 |
|
| 1101 |
-
# Initialize the agent
|
| 1102 |
agent = MarkdownToPDFAgent()
|
| 1103 |
|
| 1104 |
# Configure converter
|
|
@@ -1108,17 +892,12 @@ def process_markdown(markdown_text, page_size, font_size, font_name,
|
|
| 1108 |
base_font_size=font_size,
|
| 1109 |
font_name=font_name,
|
| 1110 |
margins=(margin_size, margin_size, margin_size, margin_size),
|
| 1111 |
-
include_toc=include_toc
|
| 1112 |
-
color_theme=color_theme
|
| 1113 |
)
|
| 1114 |
|
| 1115 |
-
#
|
| 1116 |
enhance = False
|
| 1117 |
-
|
| 1118 |
-
if use_ai:
|
| 1119 |
-
# Use the API key from Hugging Face secrets if available
|
| 1120 |
-
api_key = GEMINI_API_KEY
|
| 1121 |
-
|
| 1122 |
if api_key:
|
| 1123 |
success = agent.setup_from_gemini(api_key)
|
| 1124 |
enhance = success
|
|
@@ -1134,9 +913,8 @@ def process_markdown(markdown_text, page_size, font_size, font_name,
|
|
| 1134 |
temp_md_path,
|
| 1135 |
output_path,
|
| 1136 |
enhance=enhance,
|
| 1137 |
-
enhancement_instructions=enhancement_instructions,
|
| 1138 |
-
page_size=page_size.lower()
|
| 1139 |
-
color_theme=color_theme
|
| 1140 |
)
|
| 1141 |
|
| 1142 |
# Remove the temporary md file
|
|
@@ -1150,116 +928,29 @@ def process_markdown(markdown_text, page_size, font_size, font_name,
|
|
| 1150 |
logger.error(f"Error processing markdown: {e}")
|
| 1151 |
return None
|
| 1152 |
|
| 1153 |
-
|
| 1154 |
-
|
| 1155 |
-
|
| 1156 |
-
return ""
|
| 1157 |
-
|
| 1158 |
-
# Get the first file
|
| 1159 |
-
file = files[0]
|
| 1160 |
-
|
| 1161 |
-
try:
|
| 1162 |
-
# Read the file content
|
| 1163 |
-
if hasattr(file, 'read'):
|
| 1164 |
-
# File is a file-like object
|
| 1165 |
-
content = file.read().decode('utf-8')
|
| 1166 |
-
else:
|
| 1167 |
-
# File is a path
|
| 1168 |
-
with open(file, 'r', encoding='utf-8') as f:
|
| 1169 |
-
content = f.read()
|
| 1170 |
-
|
| 1171 |
-
return content
|
| 1172 |
-
except Exception as e:
|
| 1173 |
-
logger.error(f"Error reading uploaded file: {e}")
|
| 1174 |
-
return f"Error reading file: {str(e)}"
|
| 1175 |
-
|
| 1176 |
-
# Define custom CSS for improved UI
|
| 1177 |
-
custom_css = """
|
| 1178 |
-
.gradio-container {
|
| 1179 |
-
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
|
| 1180 |
-
}
|
| 1181 |
-
|
| 1182 |
-
.app-header {
|
| 1183 |
-
text-align: center;
|
| 1184 |
-
margin-bottom: 2rem;
|
| 1185 |
-
}
|
| 1186 |
-
|
| 1187 |
-
.app-header h1 {
|
| 1188 |
-
font-size: 2.5rem;
|
| 1189 |
-
font-weight: 700;
|
| 1190 |
-
color: #2E7D32;
|
| 1191 |
-
margin-bottom: 0.5rem;
|
| 1192 |
-
}
|
| 1193 |
-
|
| 1194 |
-
.app-header p {
|
| 1195 |
-
font-size: 1.1rem;
|
| 1196 |
-
color: #6c757d;
|
| 1197 |
-
}
|
| 1198 |
-
|
| 1199 |
-
.template-selector {
|
| 1200 |
-
margin-bottom: 1rem;
|
| 1201 |
-
}
|
| 1202 |
-
|
| 1203 |
-
footer {
|
| 1204 |
-
text-align: center;
|
| 1205 |
-
margin-top: 2rem;
|
| 1206 |
-
padding-top: 1rem;
|
| 1207 |
-
border-top: 1px solid #e9ecef;
|
| 1208 |
-
color: #6c757d;
|
| 1209 |
-
}
|
| 1210 |
-
|
| 1211 |
-
.output-container {
|
| 1212 |
-
border: 1px solid #e9ecef;
|
| 1213 |
-
border-radius: 0.5rem;
|
| 1214 |
-
padding: 1rem;
|
| 1215 |
-
margin-top: 1rem;
|
| 1216 |
-
}
|
| 1217 |
-
"""
|
| 1218 |
-
|
| 1219 |
-
# Define the Gradio interface - Using a simpler structure without tabs to fix the error
|
| 1220 |
-
with gr.Blocks(title="MarkdownMuse", css=custom_css, theme=gr.themes.Soft()) as app:
|
| 1221 |
-
# App header
|
| 1222 |
-
gr.HTML(
|
| 1223 |
"""
|
| 1224 |
-
|
| 1225 |
-
|
| 1226 |
-
|
| 1227 |
-
</div>
|
| 1228 |
"""
|
| 1229 |
)
|
| 1230 |
|
| 1231 |
-
# Two column layout - left for input, right for templates/settings
|
| 1232 |
with gr.Row():
|
| 1233 |
-
|
| 1234 |
-
|
|
|
|
| 1235 |
markdown_input = gr.TextArea(
|
| 1236 |
placeholder="Enter your markdown content here...",
|
| 1237 |
label="Markdown Content",
|
| 1238 |
-
lines=
|
| 1239 |
-
elem_id="markdown-input"
|
| 1240 |
)
|
| 1241 |
|
| 1242 |
-
|
| 1243 |
-
gr.Markdown("### π€ Or upload a file")
|
| 1244 |
-
uploaded_file = gr.File(
|
| 1245 |
-
label="Upload Markdown File",
|
| 1246 |
-
file_types=[".md", ".markdown", ".txt"],
|
| 1247 |
-
file_count="single"
|
| 1248 |
-
)
|
| 1249 |
-
upload_btn = gr.Button("Load from File")
|
| 1250 |
|
| 1251 |
-
|
| 1252 |
-
with gr.Column(scale=1):
|
| 1253 |
-
gr.Markdown("### π Templates")
|
| 1254 |
-
template_selector = gr.Dropdown(
|
| 1255 |
-
choices=["Basic", "Report", "Documentation", "Article"],
|
| 1256 |
-
value="Basic",
|
| 1257 |
-
label="Choose a template",
|
| 1258 |
-
elem_classes="template-selector"
|
| 1259 |
-
)
|
| 1260 |
-
sample_btn = gr.Button("Load Template")
|
| 1261 |
-
|
| 1262 |
-
with gr.Accordion("βοΈ PDF Settings", open=True):
|
| 1263 |
page_size = gr.Radio(
|
| 1264 |
["A4", "Letter"],
|
| 1265 |
label="Page Size",
|
|
@@ -1293,21 +984,18 @@ with gr.Blocks(title="MarkdownMuse", css=custom_css, theme=gr.themes.Soft()) as
|
|
| 1293 |
label="Include Table of Contents"
|
| 1294 |
)
|
| 1295 |
|
| 1296 |
-
color_theme = gr.Dropdown(
|
| 1297 |
-
["Default", "Modern", "Professional", "Creative", "Elegant"],
|
| 1298 |
-
value="Modern",
|
| 1299 |
-
label="Color Theme"
|
| 1300 |
-
)
|
| 1301 |
-
|
| 1302 |
with gr.Accordion("π§ AI Enhancement", open=False):
|
| 1303 |
use_ai = gr.Checkbox(
|
| 1304 |
-
value=
|
| 1305 |
label="Enable AI Enhancement"
|
| 1306 |
)
|
| 1307 |
|
| 1308 |
-
|
| 1309 |
-
|
| 1310 |
-
|
|
|
|
|
|
|
|
|
|
| 1311 |
|
| 1312 |
enhancement_instructions = gr.TextArea(
|
| 1313 |
placeholder="Optional: Provide specific instructions for how the AI should enhance your markdown...",
|
|
@@ -1315,25 +1003,24 @@ with gr.Blocks(title="MarkdownMuse", css=custom_css, theme=gr.themes.Soft()) as
|
|
| 1315 |
lines=3,
|
| 1316 |
visible=True
|
| 1317 |
)
|
| 1318 |
-
|
| 1319 |
-
|
| 1320 |
-
|
| 1321 |
-
|
|
|
|
|
|
|
|
|
|
| 1322 |
|
| 1323 |
# Set up event handlers
|
| 1324 |
-
sample_btn.click(
|
| 1325 |
-
load_sample,
|
| 1326 |
-
inputs=[template_selector],
|
| 1327 |
-
outputs=markdown_input
|
| 1328 |
-
)
|
| 1329 |
|
| 1330 |
-
|
| 1331 |
-
|
| 1332 |
-
|
| 1333 |
-
|
|
|
|
| 1334 |
)
|
| 1335 |
|
| 1336 |
-
# Connect the conversion button to the processing function
|
| 1337 |
convert_btn.click(
|
| 1338 |
process_markdown,
|
| 1339 |
inputs=[
|
|
@@ -1343,20 +1030,20 @@ with gr.Blocks(title="MarkdownMuse", css=custom_css, theme=gr.themes.Soft()) as
|
|
| 1343 |
font_name,
|
| 1344 |
margin_size,
|
| 1345 |
include_toc,
|
| 1346 |
-
color_theme,
|
| 1347 |
use_ai,
|
|
|
|
| 1348 |
enhancement_instructions
|
| 1349 |
],
|
| 1350 |
outputs=output_pdf
|
| 1351 |
)
|
| 1352 |
|
| 1353 |
-
|
| 1354 |
-
|
| 1355 |
-
|
| 1356 |
-
|
| 1357 |
-
|
| 1358 |
-
|
| 1359 |
-
|
| 1360 |
"""
|
| 1361 |
)
|
| 1362 |
|
|
|
|
| 23 |
from PIL import Image as PilImage
|
| 24 |
import io
|
| 25 |
import tempfile
|
|
|
|
| 26 |
from datetime import datetime
|
| 27 |
|
| 28 |
# Set up logging
|
|
|
|
| 32 |
)
|
| 33 |
logger = logging.getLogger(__name__)
|
| 34 |
|
|
|
|
|
|
|
|
|
|
| 35 |
class MarkdownToPDFConverter:
|
| 36 |
"""
|
| 37 |
Class to convert Markdown content to PDF using ReportLab.
|
|
|
|
| 45 |
base_font_size: int = 10,
|
| 46 |
heading_scale: Dict[int, float] = None,
|
| 47 |
include_toc: bool = True,
|
| 48 |
+
code_style: str = "github"
|
|
|
|
| 49 |
):
|
| 50 |
"""
|
| 51 |
Initialize the converter with configuration options.
|
|
|
|
| 59 |
heading_scale: Dictionary of heading levels to font size multipliers
|
| 60 |
include_toc: Whether to include a table of contents
|
| 61 |
code_style: Style to use for code blocks
|
|
|
|
| 62 |
"""
|
| 63 |
self.output_path = output_path
|
| 64 |
self.page_size = A4 if page_size.upper() == "A4" else letter
|
|
|
|
| 75 |
}
|
| 76 |
self.include_toc = include_toc
|
| 77 |
self.code_style = code_style
|
|
|
|
| 78 |
|
| 79 |
# Initialize styles
|
| 80 |
self.styles = getSampleStyleSheet()
|
|
|
|
| 86 |
|
| 87 |
def _setup_styles(self) -> None:
|
| 88 |
"""Set up custom paragraph styles for the document."""
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 89 |
# Modify existing Normal style
|
| 90 |
self.styles['Normal'].fontName = self.font_name
|
| 91 |
self.styles['Normal'].fontSize = self.base_font_size
|
|
|
|
| 106 |
self.styles[heading_name].leading = int(self.base_font_size * size_multiplier * 1.2)
|
| 107 |
self.styles[heading_name].spaceAfter = self.base_font_size
|
| 108 |
self.styles[heading_name].spaceBefore = self.base_font_size * (1 + (0.2 * (7 - level)))
|
|
|
|
| 109 |
else:
|
| 110 |
# Create new style
|
| 111 |
self.styles.add(
|
|
|
|
| 117 |
leading=int(self.base_font_size * size_multiplier * 1.2),
|
| 118 |
spaceAfter=self.base_font_size,
|
| 119 |
spaceBefore=self.base_font_size * (1 + (0.2 * (7 - level))),
|
|
|
|
| 120 |
)
|
| 121 |
)
|
| 122 |
|
|
|
|
| 130 |
spaceAfter=self.base_font_size,
|
| 131 |
spaceBefore=self.base_font_size,
|
| 132 |
leftIndent=self.base_font_size,
|
| 133 |
+
backgroundColor=HexColor('#EEEEEE'),
|
| 134 |
borderWidth=0,
|
| 135 |
borderPadding=self.base_font_size * 0.5,
|
| 136 |
)
|
| 137 |
)
|
| 138 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 139 |
# List item style
|
| 140 |
self.styles.add(
|
| 141 |
ParagraphStyle(
|
|
|
|
| 153 |
parent=self.styles['Heading1'],
|
| 154 |
fontSize=int(self.base_font_size * 1.5),
|
| 155 |
spaceAfter=self.base_font_size * 1.5,
|
|
|
|
| 156 |
)
|
| 157 |
)
|
| 158 |
|
|
|
|
| 242 |
base_font_size=self.base_font_size,
|
| 243 |
heading_scale=self.heading_scale,
|
| 244 |
include_toc=separate_toc,
|
| 245 |
+
code_style=self.code_style
|
|
|
|
| 246 |
)
|
| 247 |
converter.convert_file(file_path)
|
| 248 |
|
|
|
|
| 318 |
Preformatted(code, self.styles['CodeBlock'])
|
| 319 |
)
|
| 320 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 321 |
elif element.name == 'img':
|
| 322 |
src = element.get('src', '')
|
| 323 |
alt = element.get('alt', 'Image')
|
|
|
|
| 378 |
|
| 379 |
elif element.name == 'ul' or element.name == 'ol':
|
| 380 |
list_items = []
|
| 381 |
+
bullet_type = 'bullet' if element.name == 'ul' else 'numbered'
|
| 382 |
|
| 383 |
for item in element.find_all('li', recursive=False):
|
| 384 |
text = self._process_inline_elements(item)
|
|
|
|
| 389 |
)
|
| 390 |
)
|
| 391 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 392 |
self.elements.append(
|
| 393 |
ListFlowable(
|
| 394 |
list_items,
|
| 395 |
+
bulletType=bullet_type,
|
| 396 |
+
start=1 if bullet_type == 'numbered' else None,
|
| 397 |
+
bulletFormat='%s.' if bullet_type == 'numbered' else '%s'
|
| 398 |
)
|
| 399 |
)
|
| 400 |
|
|
|
|
| 592 |
try:
|
| 593 |
from langchain_google_genai import ChatGoogleGenerativeAI
|
| 594 |
|
| 595 |
+
api_key = api_key or os.getenv("GOOGLE_API_KEY")
|
| 596 |
if not api_key:
|
| 597 |
logger.warning("No Google API key provided. Agent will run without LLM enhancement.")
|
| 598 |
return False
|
|
|
|
| 694 |
return '\n'.join(result_lines)
|
| 695 |
|
| 696 |
def process_file(self, input_path: str, output_path: str = None, enhance: bool = False,
|
| 697 |
+
enhancement_instructions: str = None, page_size: str = "A4") -> str:
|
|
|
|
| 698 |
"""
|
| 699 |
Process a single markdown file and convert it to PDF.
|
| 700 |
|
|
|
|
| 704 |
enhance: Whether to enhance the content with LLM
|
| 705 |
enhancement_instructions: Specific instructions for enhancement
|
| 706 |
page_size: Page size for the PDF ("A4" or "letter")
|
|
|
|
| 707 |
|
| 708 |
Returns:
|
| 709 |
Path to the generated PDF
|
|
|
|
| 729 |
# Configure converter
|
| 730 |
self.converter = MarkdownToPDFConverter(
|
| 731 |
output_path=output_path,
|
| 732 |
+
page_size=page_size
|
|
|
|
| 733 |
)
|
| 734 |
|
| 735 |
# Convert to PDF
|
|
|
|
| 741 |
def process_directory(self, input_dir: str, output_dir: str = None, pattern: str = "*.md",
|
| 742 |
enhance: bool = False, merge: bool = False,
|
| 743 |
output_filename: str = "merged_document.pdf",
|
| 744 |
+
page_size: str = "A4") -> List[str]:
|
| 745 |
"""
|
| 746 |
Process all markdown files in a directory.
|
| 747 |
|
|
|
|
| 753 |
merge: Whether to merge all files into a single PDF
|
| 754 |
output_filename: Filename for merged PDF
|
| 755 |
page_size: Page size for the PDF ("A4" or "letter")
|
|
|
|
| 756 |
|
| 757 |
Returns:
|
| 758 |
List of paths to generated PDFs
|
|
|
|
| 804 |
output_path = os.path.join(output_dir, output_filename)
|
| 805 |
self.converter = MarkdownToPDFConverter(
|
| 806 |
output_path=output_path,
|
| 807 |
+
page_size=page_size
|
|
|
|
| 808 |
)
|
| 809 |
self.converter.convert_content(merged_content)
|
| 810 |
|
|
|
|
| 814 |
output_path = os.path.join(output_dir, output_filename)
|
| 815 |
self.converter = MarkdownToPDFConverter(
|
| 816 |
output_path=output_path,
|
| 817 |
+
page_size=page_size
|
|
|
|
| 818 |
)
|
| 819 |
self.converter.convert_multiple_files(md_files, merge=True)
|
| 820 |
|
|
|
|
| 831 |
md_file,
|
| 832 |
output_path,
|
| 833 |
enhance=enhance,
|
| 834 |
+
page_size=page_size
|
|
|
|
| 835 |
)
|
| 836 |
|
| 837 |
if processed_file:
|
|
|
|
| 839 |
|
| 840 |
return output_files
|
| 841 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 842 |
# Gradio Interface for Hugging Face
|
| 843 |
+
def load_sample():
|
| 844 |
+
"""Load a sample markdown document."""
|
| 845 |
+
return """# Sample Markdown Document
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 846 |
|
| 847 |
## Introduction
|
| 848 |
This is a sample markdown document to demonstrate the capabilities of **MarkdownMuse**. You can use this as a starting point for your own documents.
|
|
|
|
| 870 |
"""
|
| 871 |
|
| 872 |
def process_markdown(markdown_text, page_size, font_size, font_name,
|
| 873 |
+
margin_size, include_toc, use_ai, api_key, enhancement_instructions):
|
|
|
|
| 874 |
"""
|
| 875 |
Process markdown text and generate a PDF.
|
| 876 |
|
|
|
|
| 882 |
output_path = temp_file.name
|
| 883 |
temp_file.close()
|
| 884 |
|
| 885 |
+
# Initialize the agent and process the markdown
|
| 886 |
agent = MarkdownToPDFAgent()
|
| 887 |
|
| 888 |
# Configure converter
|
|
|
|
| 892 |
base_font_size=font_size,
|
| 893 |
font_name=font_name,
|
| 894 |
margins=(margin_size, margin_size, margin_size, margin_size),
|
| 895 |
+
include_toc=include_toc
|
|
|
|
| 896 |
)
|
| 897 |
|
| 898 |
+
# Setup AI enhancement if requested
|
| 899 |
enhance = False
|
| 900 |
+
if use_ai and api_key:
|
|
|
|
|
|
|
|
|
|
|
|
|
| 901 |
if api_key:
|
| 902 |
success = agent.setup_from_gemini(api_key)
|
| 903 |
enhance = success
|
|
|
|
| 913 |
temp_md_path,
|
| 914 |
output_path,
|
| 915 |
enhance=enhance,
|
| 916 |
+
enhancement_instructions=enhancement_instructions if enhancement_instructions else None,
|
| 917 |
+
page_size=page_size.lower()
|
|
|
|
| 918 |
)
|
| 919 |
|
| 920 |
# Remove the temporary md file
|
|
|
|
| 928 |
logger.error(f"Error processing markdown: {e}")
|
| 929 |
return None
|
| 930 |
|
| 931 |
+
# Define the Gradio interface
|
| 932 |
+
with gr.Blocks(title="MarkdownMuse", theme=gr.themes.Soft()) as app:
|
| 933 |
+
gr.Markdown(
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 934 |
"""
|
| 935 |
+
# π MarkdownMuse
|
| 936 |
+
|
| 937 |
+
Transform your Markdown files into beautifully formatted PDFs with ease.
|
|
|
|
| 938 |
"""
|
| 939 |
)
|
| 940 |
|
|
|
|
| 941 |
with gr.Row():
|
| 942 |
+
with gr.Column(scale=1):
|
| 943 |
+
gr.Markdown("### π Input")
|
| 944 |
+
|
| 945 |
markdown_input = gr.TextArea(
|
| 946 |
placeholder="Enter your markdown content here...",
|
| 947 |
label="Markdown Content",
|
| 948 |
+
lines=15
|
|
|
|
| 949 |
)
|
| 950 |
|
| 951 |
+
sample_btn = gr.Button("π Load Sample")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 952 |
|
| 953 |
+
with gr.Accordion("βοΈ PDF Settings", open=False):
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 954 |
page_size = gr.Radio(
|
| 955 |
["A4", "Letter"],
|
| 956 |
label="Page Size",
|
|
|
|
| 984 |
label="Include Table of Contents"
|
| 985 |
)
|
| 986 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 987 |
with gr.Accordion("π§ AI Enhancement", open=False):
|
| 988 |
use_ai = gr.Checkbox(
|
| 989 |
+
value=False,
|
| 990 |
label="Enable AI Enhancement"
|
| 991 |
)
|
| 992 |
|
| 993 |
+
api_key = gr.Textbox(
|
| 994 |
+
placeholder="Enter your Google Gemini API key...",
|
| 995 |
+
label="Google Gemini API Key",
|
| 996 |
+
type="password",
|
| 997 |
+
visible=True
|
| 998 |
+
)
|
| 999 |
|
| 1000 |
enhancement_instructions = gr.TextArea(
|
| 1001 |
placeholder="Optional: Provide specific instructions for how the AI should enhance your markdown...",
|
|
|
|
| 1003 |
lines=3,
|
| 1004 |
visible=True
|
| 1005 |
)
|
| 1006 |
+
|
| 1007 |
+
convert_btn = gr.Button("π Convert to PDF", variant="primary")
|
| 1008 |
+
|
| 1009 |
+
with gr.Column(scale=1):
|
| 1010 |
+
gr.Markdown("### π Output")
|
| 1011 |
+
|
| 1012 |
+
output_pdf = gr.File(label="Generated PDF")
|
| 1013 |
|
| 1014 |
# Set up event handlers
|
| 1015 |
+
sample_btn.click(load_sample, outputs=markdown_input)
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1016 |
|
| 1017 |
+
# Add visibility toggle for API key based on checkbox
|
| 1018 |
+
use_ai.change(
|
| 1019 |
+
lambda x: [gr.update(visible=x), gr.update(visible=x)],
|
| 1020 |
+
inputs=[use_ai],
|
| 1021 |
+
outputs=[api_key, enhancement_instructions]
|
| 1022 |
)
|
| 1023 |
|
|
|
|
| 1024 |
convert_btn.click(
|
| 1025 |
process_markdown,
|
| 1026 |
inputs=[
|
|
|
|
| 1030 |
font_name,
|
| 1031 |
margin_size,
|
| 1032 |
include_toc,
|
|
|
|
| 1033 |
use_ai,
|
| 1034 |
+
api_key,
|
| 1035 |
enhancement_instructions
|
| 1036 |
],
|
| 1037 |
outputs=output_pdf
|
| 1038 |
)
|
| 1039 |
|
| 1040 |
+
gr.Markdown(
|
| 1041 |
+
"""
|
| 1042 |
+
### π About MarkdownMuse
|
| 1043 |
+
|
| 1044 |
+
This tool allows you to convert Markdown documents to beautifully formatted PDFs. Use the options to customize your output.
|
| 1045 |
+
|
| 1046 |
+
Made with β€οΈ using Gradio and ReportLab | Β© 2024
|
| 1047 |
"""
|
| 1048 |
)
|
| 1049 |
|