quantalogic 0.35.0__py3-none-any.whl → 0.50.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- quantalogic/__init__.py +0 -4
- quantalogic/agent.py +603 -363
- quantalogic/agent_config.py +233 -46
- quantalogic/agent_factory.py +34 -22
- quantalogic/coding_agent.py +16 -14
- quantalogic/config.py +2 -1
- quantalogic/console_print_events.py +4 -8
- quantalogic/console_print_token.py +2 -2
- quantalogic/docs_cli.py +15 -10
- quantalogic/event_emitter.py +258 -83
- quantalogic/flow/__init__.py +23 -0
- quantalogic/flow/flow.py +595 -0
- quantalogic/flow/flow_extractor.py +672 -0
- quantalogic/flow/flow_generator.py +89 -0
- quantalogic/flow/flow_manager.py +407 -0
- quantalogic/flow/flow_manager_schema.py +169 -0
- quantalogic/flow/flow_yaml.md +419 -0
- quantalogic/generative_model.py +109 -77
- quantalogic/get_model_info.py +5 -5
- quantalogic/interactive_text_editor.py +100 -73
- quantalogic/main.py +17 -21
- quantalogic/model_info_list.py +3 -3
- quantalogic/model_info_litellm.py +14 -14
- quantalogic/prompts.py +2 -1
- quantalogic/{llm.py → quantlitellm.py} +29 -39
- quantalogic/search_agent.py +4 -4
- quantalogic/server/models.py +4 -1
- quantalogic/task_file_reader.py +5 -5
- quantalogic/task_runner.py +20 -20
- quantalogic/tool_manager.py +10 -21
- quantalogic/tools/__init__.py +98 -68
- quantalogic/tools/composio/composio.py +416 -0
- quantalogic/tools/{generate_database_report_tool.py → database/generate_database_report_tool.py} +4 -9
- quantalogic/tools/database/sql_query_tool_advanced.py +261 -0
- quantalogic/tools/document_tools/markdown_to_docx_tool.py +620 -0
- quantalogic/tools/document_tools/markdown_to_epub_tool.py +438 -0
- quantalogic/tools/document_tools/markdown_to_html_tool.py +362 -0
- quantalogic/tools/document_tools/markdown_to_ipynb_tool.py +319 -0
- quantalogic/tools/document_tools/markdown_to_latex_tool.py +420 -0
- quantalogic/tools/document_tools/markdown_to_pdf_tool.py +623 -0
- quantalogic/tools/document_tools/markdown_to_pptx_tool.py +319 -0
- quantalogic/tools/duckduckgo_search_tool.py +2 -4
- quantalogic/tools/finance/alpha_vantage_tool.py +440 -0
- quantalogic/tools/finance/ccxt_tool.py +373 -0
- quantalogic/tools/finance/finance_llm_tool.py +387 -0
- quantalogic/tools/finance/google_finance.py +192 -0
- quantalogic/tools/finance/market_intelligence_tool.py +520 -0
- quantalogic/tools/finance/technical_analysis_tool.py +491 -0
- quantalogic/tools/finance/tradingview_tool.py +336 -0
- quantalogic/tools/finance/yahoo_finance.py +236 -0
- quantalogic/tools/git/bitbucket_clone_repo_tool.py +181 -0
- quantalogic/tools/git/bitbucket_operations_tool.py +326 -0
- quantalogic/tools/git/clone_repo_tool.py +189 -0
- quantalogic/tools/git/git_operations_tool.py +532 -0
- quantalogic/tools/google_packages/google_news_tool.py +480 -0
- quantalogic/tools/grep_app_tool.py +123 -186
- quantalogic/tools/{dalle_e.py → image_generation/dalle_e.py} +37 -27
- quantalogic/tools/jinja_tool.py +6 -10
- quantalogic/tools/language_handlers/__init__.py +22 -9
- quantalogic/tools/list_directory_tool.py +131 -42
- quantalogic/tools/llm_tool.py +45 -15
- quantalogic/tools/llm_vision_tool.py +59 -7
- quantalogic/tools/markitdown_tool.py +17 -5
- quantalogic/tools/nasa_packages/models.py +47 -0
- quantalogic/tools/nasa_packages/nasa_apod_tool.py +232 -0
- quantalogic/tools/nasa_packages/nasa_neows_tool.py +147 -0
- quantalogic/tools/nasa_packages/services.py +82 -0
- quantalogic/tools/presentation_tools/presentation_llm_tool.py +396 -0
- quantalogic/tools/product_hunt/product_hunt_tool.py +258 -0
- quantalogic/tools/product_hunt/services.py +63 -0
- quantalogic/tools/rag_tool/__init__.py +48 -0
- quantalogic/tools/rag_tool/document_metadata.py +15 -0
- quantalogic/tools/rag_tool/query_response.py +20 -0
- quantalogic/tools/rag_tool/rag_tool.py +566 -0
- quantalogic/tools/rag_tool/rag_tool_beta.py +264 -0
- quantalogic/tools/read_html_tool.py +24 -38
- quantalogic/tools/replace_in_file_tool.py +10 -10
- quantalogic/tools/safe_python_interpreter_tool.py +10 -24
- quantalogic/tools/search_definition_names.py +2 -2
- quantalogic/tools/sequence_tool.py +14 -23
- quantalogic/tools/sql_query_tool.py +17 -19
- quantalogic/tools/tool.py +39 -15
- quantalogic/tools/unified_diff_tool.py +1 -1
- quantalogic/tools/utilities/csv_processor_tool.py +234 -0
- quantalogic/tools/utilities/download_file_tool.py +179 -0
- quantalogic/tools/utilities/mermaid_validator_tool.py +661 -0
- quantalogic/tools/utils/__init__.py +1 -4
- quantalogic/tools/utils/create_sample_database.py +24 -38
- quantalogic/tools/utils/generate_database_report.py +74 -82
- quantalogic/tools/wikipedia_search_tool.py +17 -21
- quantalogic/utils/ask_user_validation.py +1 -1
- quantalogic/utils/async_utils.py +35 -0
- quantalogic/utils/check_version.py +3 -5
- quantalogic/utils/get_all_models.py +2 -1
- quantalogic/utils/git_ls.py +21 -7
- quantalogic/utils/lm_studio_model_info.py +9 -7
- quantalogic/utils/python_interpreter.py +113 -43
- quantalogic/utils/xml_utility.py +178 -0
- quantalogic/version_check.py +1 -1
- quantalogic/welcome_message.py +7 -7
- quantalogic/xml_parser.py +0 -1
- {quantalogic-0.35.0.dist-info → quantalogic-0.50.0.dist-info}/METADATA +40 -1
- quantalogic-0.50.0.dist-info/RECORD +148 -0
- quantalogic-0.35.0.dist-info/RECORD +0 -102
- {quantalogic-0.35.0.dist-info → quantalogic-0.50.0.dist-info}/LICENSE +0 -0
- {quantalogic-0.35.0.dist-info → quantalogic-0.50.0.dist-info}/WHEEL +0 -0
- {quantalogic-0.35.0.dist-info → quantalogic-0.50.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,438 @@
|
|
1
|
+
"""Tool for converting markdown content to well-structured ePub documents.
|
2
|
+
|
3
|
+
Why this tool:
|
4
|
+
- Provides a standardized way to convert markdown to professional ePub books
|
5
|
+
- Supports rich formatting and interactive elements
|
6
|
+
- Creates responsive e-books that work on all devices
|
7
|
+
- Handles chapters, table of contents, and metadata
|
8
|
+
- Includes support for custom styling and themes
|
9
|
+
"""
|
10
|
+
|
11
|
+
import json
|
12
|
+
import os
|
13
|
+
import tempfile
|
14
|
+
import uuid
|
15
|
+
from pathlib import Path
|
16
|
+
from typing import ClassVar, Dict, List, Optional
|
17
|
+
|
18
|
+
import markdown
|
19
|
+
import mermaid
|
20
|
+
from bs4 import BeautifulSoup
|
21
|
+
from ebooklib import epub
|
22
|
+
from loguru import logger
|
23
|
+
|
24
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
25
|
+
|
26
|
+
|
27
|
+
class MarkdownToEpubTool(Tool):
|
28
|
+
"""Converts markdown to professional EPUB documents with advanced formatting."""
|
29
|
+
|
30
|
+
model_config = {
|
31
|
+
"arbitrary_types_allowed": True
|
32
|
+
}
|
33
|
+
|
34
|
+
name: str = "markdown_to_epub_tool"
|
35
|
+
description: str = (
|
36
|
+
"Converts markdown to EPUB with support for images, Mermaid diagrams, "
|
37
|
+
"code blocks, tables, and advanced styling."
|
38
|
+
)
|
39
|
+
need_validation: bool = False
|
40
|
+
|
41
|
+
arguments: List[ToolArgument] = [
|
42
|
+
ToolArgument(
|
43
|
+
name="markdown_content",
|
44
|
+
arg_type="string",
|
45
|
+
description="Markdown content with optional chapter separators (---)",
|
46
|
+
required=True,
|
47
|
+
example="# Book Title\n\n## Chapter 1\n\nContent...\n\n---\n\n## Chapter 2\n\nMore content...",
|
48
|
+
),
|
49
|
+
ToolArgument(
|
50
|
+
name="output_path",
|
51
|
+
arg_type="string",
|
52
|
+
description="Path for saving the EPUB file",
|
53
|
+
required=True,
|
54
|
+
example="/path/to/output.epub",
|
55
|
+
),
|
56
|
+
ToolArgument(
|
57
|
+
name="metadata",
|
58
|
+
arg_type="string",
|
59
|
+
description="JSON string with book metadata",
|
60
|
+
required=False,
|
61
|
+
example='{"title": "My Book", "author": "John Doe", "language": "en"}',
|
62
|
+
),
|
63
|
+
ToolArgument(
|
64
|
+
name="cover_image",
|
65
|
+
arg_type="string",
|
66
|
+
description="Path to cover image file",
|
67
|
+
required=False,
|
68
|
+
example="path/to/cover.jpg",
|
69
|
+
),
|
70
|
+
ToolArgument(
|
71
|
+
name="style_config",
|
72
|
+
arg_type="string",
|
73
|
+
description="JSON string with style settings",
|
74
|
+
required=False,
|
75
|
+
example='{"theme": "light", "font_family": "Literata"}',
|
76
|
+
),
|
77
|
+
]
|
78
|
+
|
79
|
+
# Default style configuration
|
80
|
+
DEFAULT_CSS: ClassVar[str] = """
|
81
|
+
@import url('https://fonts.googleapis.com/css2?family=Literata:ital,wght@0,400;0,600;1,400&family=Source+Code+Pro&display=swap');
|
82
|
+
|
83
|
+
:root {
|
84
|
+
color-scheme: %(theme)s;
|
85
|
+
}
|
86
|
+
|
87
|
+
body {
|
88
|
+
font-family: %(font_family)s;
|
89
|
+
line-height: %(line_height)s;
|
90
|
+
color: %(text_color)s;
|
91
|
+
background-color: %(background_color)s;
|
92
|
+
margin: 0 auto;
|
93
|
+
max-width: %(max_width)s;
|
94
|
+
padding: 1em;
|
95
|
+
}
|
96
|
+
|
97
|
+
h1, h2, h3, h4, h5, h6 {
|
98
|
+
font-family: %(heading_font)s;
|
99
|
+
color: %(heading_color)s;
|
100
|
+
margin-top: 1.5em;
|
101
|
+
margin-bottom: 0.5em;
|
102
|
+
}
|
103
|
+
|
104
|
+
a {
|
105
|
+
color: %(link_color)s;
|
106
|
+
text-decoration: none;
|
107
|
+
}
|
108
|
+
|
109
|
+
pre, code {
|
110
|
+
font-family: %(code_font)s;
|
111
|
+
background-color: #f6f8fa;
|
112
|
+
border-radius: 3px;
|
113
|
+
font-size: 0.9em;
|
114
|
+
}
|
115
|
+
|
116
|
+
pre {
|
117
|
+
padding: 1em;
|
118
|
+
overflow-x: auto;
|
119
|
+
line-height: 1.45;
|
120
|
+
}
|
121
|
+
|
122
|
+
code {
|
123
|
+
padding: 0.2em 0.4em;
|
124
|
+
}
|
125
|
+
|
126
|
+
img {
|
127
|
+
max-width: 100%%;
|
128
|
+
height: auto;
|
129
|
+
display: block;
|
130
|
+
margin: 1em auto;
|
131
|
+
}
|
132
|
+
|
133
|
+
blockquote {
|
134
|
+
margin: 1em 0;
|
135
|
+
padding-left: 1em;
|
136
|
+
border-left: 4px solid %(link_color)s;
|
137
|
+
color: #666666;
|
138
|
+
}
|
139
|
+
|
140
|
+
table {
|
141
|
+
width: 100%%;
|
142
|
+
border-collapse: collapse;
|
143
|
+
margin: 1em 0;
|
144
|
+
}
|
145
|
+
|
146
|
+
th, td {
|
147
|
+
border: 1px solid #ddd;
|
148
|
+
padding: 8px;
|
149
|
+
text-align: left;
|
150
|
+
}
|
151
|
+
|
152
|
+
th {
|
153
|
+
background-color: #f6f8fa;
|
154
|
+
}
|
155
|
+
|
156
|
+
.chapter-title {
|
157
|
+
text-align: center;
|
158
|
+
margin-top: 3em;
|
159
|
+
margin-bottom: 2em;
|
160
|
+
}
|
161
|
+
|
162
|
+
.chapter {
|
163
|
+
break-before: page;
|
164
|
+
}
|
165
|
+
|
166
|
+
@media (prefers-color-scheme: dark) {
|
167
|
+
body {
|
168
|
+
background-color: #1a1a1a;
|
169
|
+
color: #e6e6e6;
|
170
|
+
}
|
171
|
+
|
172
|
+
pre, code {
|
173
|
+
background-color: #2d2d2d;
|
174
|
+
}
|
175
|
+
|
176
|
+
th {
|
177
|
+
background-color: #2d2d2d;
|
178
|
+
}
|
179
|
+
|
180
|
+
blockquote {
|
181
|
+
color: #b3b3b3;
|
182
|
+
}
|
183
|
+
}
|
184
|
+
"""
|
185
|
+
|
186
|
+
DEFAULT_STYLES: ClassVar[Dict[str, str]] = {
|
187
|
+
"theme": "light",
|
188
|
+
"font_family": "'Literata', Georgia, serif",
|
189
|
+
"heading_font": "'Literata', Georgia, serif",
|
190
|
+
"code_font": "'Source Code Pro', monospace",
|
191
|
+
"line_height": "1.6",
|
192
|
+
"max_width": "45em",
|
193
|
+
"text_color": "#333333",
|
194
|
+
"heading_color": "#222222",
|
195
|
+
"link_color": "#0366d6",
|
196
|
+
"background_color": "#ffffff"
|
197
|
+
}
|
198
|
+
|
199
|
+
def _normalize_path(self, path: str) -> Path:
|
200
|
+
"""Convert path string to normalized Path object."""
|
201
|
+
if path.startswith("~"):
|
202
|
+
path = os.path.expanduser(path)
|
203
|
+
return Path(path).resolve()
|
204
|
+
|
205
|
+
def _parse_style_config(self, style_config: Optional[str]) -> Dict[str, str]:
|
206
|
+
"""Parse and validate style configuration."""
|
207
|
+
try:
|
208
|
+
if not style_config:
|
209
|
+
return self.DEFAULT_STYLES.copy()
|
210
|
+
|
211
|
+
custom_styles = json.loads(style_config)
|
212
|
+
styles = self.DEFAULT_STYLES.copy()
|
213
|
+
styles.update(custom_styles)
|
214
|
+
return styles
|
215
|
+
except json.JSONDecodeError as e:
|
216
|
+
logger.error(f"Invalid style configuration JSON: {e}")
|
217
|
+
return self.DEFAULT_STYLES.copy()
|
218
|
+
|
219
|
+
def _process_mermaid_diagrams(self, html_content: str, temp_dir: Path) -> str:
|
220
|
+
"""Convert Mermaid diagram code blocks to SVG images."""
|
221
|
+
soup = BeautifulSoup(html_content, 'html.parser')
|
222
|
+
mermaid_blocks = soup.find_all('code', class_='language-mermaid')
|
223
|
+
|
224
|
+
for block in mermaid_blocks:
|
225
|
+
try:
|
226
|
+
diagram = mermaid.generate_diagram(block.text)
|
227
|
+
img_name = f"diagram_{hash(block.text)}.svg"
|
228
|
+
img_path = temp_dir / img_name
|
229
|
+
|
230
|
+
with open(img_path, 'wb') as f:
|
231
|
+
f.write(diagram)
|
232
|
+
|
233
|
+
img_tag = soup.new_tag('img')
|
234
|
+
img_tag['src'] = img_name
|
235
|
+
img_tag['alt'] = 'Diagram'
|
236
|
+
block.parent.replace_with(img_tag)
|
237
|
+
except Exception as e:
|
238
|
+
logger.error(f"Error processing Mermaid diagram: {e}")
|
239
|
+
|
240
|
+
return str(soup)
|
241
|
+
|
242
|
+
def _split_chapters(self, content: str) -> List[Dict[str, str]]:
|
243
|
+
"""Split markdown content into chapters."""
|
244
|
+
chapters = []
|
245
|
+
current_chapter = []
|
246
|
+
current_title = "Untitled Chapter"
|
247
|
+
|
248
|
+
for line in content.split('\n'):
|
249
|
+
if line.strip() == '---':
|
250
|
+
if current_chapter:
|
251
|
+
chapters.append({
|
252
|
+
'title': current_title,
|
253
|
+
'content': '\n'.join(current_chapter)
|
254
|
+
})
|
255
|
+
current_chapter = []
|
256
|
+
current_title = "Untitled Chapter"
|
257
|
+
else:
|
258
|
+
if line.startswith('# '):
|
259
|
+
current_title = line[2:].strip()
|
260
|
+
current_chapter.append(line)
|
261
|
+
|
262
|
+
if current_chapter:
|
263
|
+
chapters.append({
|
264
|
+
'title': current_title,
|
265
|
+
'content': '\n'.join(current_chapter)
|
266
|
+
})
|
267
|
+
|
268
|
+
return chapters
|
269
|
+
|
270
|
+
def _create_nav_page(self, chapters: List[Dict[str, str]]) -> epub.EpubHtml:
|
271
|
+
"""Create table of contents navigation page."""
|
272
|
+
content = """
|
273
|
+
<h1>Table of Contents</h1>
|
274
|
+
<nav epub:type="toc">
|
275
|
+
<ol>
|
276
|
+
"""
|
277
|
+
|
278
|
+
for i, chapter in enumerate(chapters, 1):
|
279
|
+
content += f'<li><a href="chapter_{i}.xhtml">{chapter["title"]}</a></li>'
|
280
|
+
|
281
|
+
content += """
|
282
|
+
</ol>
|
283
|
+
</nav>
|
284
|
+
"""
|
285
|
+
|
286
|
+
nav = epub.EpubHtml(
|
287
|
+
title='Table of Contents',
|
288
|
+
file_name='nav.xhtml',
|
289
|
+
content=content
|
290
|
+
)
|
291
|
+
return nav
|
292
|
+
|
293
|
+
def execute(self, **kwargs) -> str:
|
294
|
+
"""Execute the markdown to EPUB conversion.
|
295
|
+
|
296
|
+
Args:
|
297
|
+
**kwargs: Tool arguments including markdown_content, output_path,
|
298
|
+
metadata, cover_image, and style_config
|
299
|
+
|
300
|
+
Returns:
|
301
|
+
Success message with output path
|
302
|
+
"""
|
303
|
+
try:
|
304
|
+
markdown_content = kwargs['markdown_content']
|
305
|
+
output_path = self._normalize_path(kwargs['output_path'])
|
306
|
+
metadata = json.loads(kwargs.get('metadata', '{}'))
|
307
|
+
cover_image = kwargs.get('cover_image')
|
308
|
+
style_config = kwargs.get('style_config')
|
309
|
+
|
310
|
+
# Create output directory
|
311
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
312
|
+
|
313
|
+
# Create temporary directory for assets
|
314
|
+
with tempfile.TemporaryDirectory() as temp_dir:
|
315
|
+
temp_path = Path(temp_dir)
|
316
|
+
|
317
|
+
# Initialize EPUB book
|
318
|
+
book = epub.EpubBook()
|
319
|
+
|
320
|
+
# Set metadata
|
321
|
+
book.set_identifier(str(uuid.uuid4()))
|
322
|
+
book.set_title(metadata.get('title', 'Untitled'))
|
323
|
+
book.set_language(metadata.get('language', 'en'))
|
324
|
+
book.add_author(metadata.get('author', 'Unknown'))
|
325
|
+
book.set_cover('cover.jpg', open(cover_image, 'rb').read()) if cover_image else None
|
326
|
+
|
327
|
+
# Parse style configuration
|
328
|
+
styles = self._parse_style_config(style_config)
|
329
|
+
css_content = self.DEFAULT_CSS % styles
|
330
|
+
|
331
|
+
# Add CSS
|
332
|
+
style = epub.EpubItem(
|
333
|
+
uid="style",
|
334
|
+
file_name="style.css",
|
335
|
+
media_type="text/css",
|
336
|
+
content=css_content
|
337
|
+
)
|
338
|
+
book.add_item(style)
|
339
|
+
|
340
|
+
# Split content into chapters
|
341
|
+
chapters = self._split_chapters(markdown_content)
|
342
|
+
epub_chapters = []
|
343
|
+
|
344
|
+
# Process chapters
|
345
|
+
for i, chapter in enumerate(chapters, 1):
|
346
|
+
# Convert markdown to HTML
|
347
|
+
html_content = markdown.markdown(
|
348
|
+
chapter['content'],
|
349
|
+
extensions=['extra', 'codehilite', 'tables', 'fenced_code']
|
350
|
+
)
|
351
|
+
|
352
|
+
# Process Mermaid diagrams
|
353
|
+
html_content = self._process_mermaid_diagrams(html_content, temp_path)
|
354
|
+
|
355
|
+
# Create chapter
|
356
|
+
epub_chapter = epub.EpubHtml(
|
357
|
+
title=chapter['title'],
|
358
|
+
file_name=f'chapter_{i}.xhtml',
|
359
|
+
content=f'''
|
360
|
+
<h1 class="chapter-title">{chapter['title']}</h1>
|
361
|
+
<div class="chapter">
|
362
|
+
{html_content}
|
363
|
+
</div>
|
364
|
+
'''
|
365
|
+
)
|
366
|
+
epub_chapter.add_item(style)
|
367
|
+
book.add_item(epub_chapter)
|
368
|
+
epub_chapters.append(epub_chapter)
|
369
|
+
|
370
|
+
# Add navigation
|
371
|
+
book.toc = [(epub.Section(chapter['title']), [c])
|
372
|
+
for chapter, c in zip(chapters, epub_chapters)]
|
373
|
+
|
374
|
+
# Add navigation page
|
375
|
+
nav = self._create_nav_page(chapters)
|
376
|
+
book.add_item(nav)
|
377
|
+
|
378
|
+
# Basic spine
|
379
|
+
book.spine = ['nav'] + epub_chapters
|
380
|
+
|
381
|
+
# Add any images from temp directory
|
382
|
+
for img_path in temp_path.glob('*.svg'):
|
383
|
+
with open(img_path, 'rb') as f:
|
384
|
+
epub_image = epub.EpubItem(
|
385
|
+
uid=f"image_{img_path.stem}",
|
386
|
+
file_name=img_path.name,
|
387
|
+
media_type="image/svg+xml",
|
388
|
+
content=f.read()
|
389
|
+
)
|
390
|
+
book.add_item(epub_image)
|
391
|
+
|
392
|
+
# Write EPUB file
|
393
|
+
epub.write_epub(str(output_path), book, {})
|
394
|
+
|
395
|
+
return f"Successfully created EPUB at: {output_path}"
|
396
|
+
|
397
|
+
except Exception as e:
|
398
|
+
error_msg = f"Error converting markdown to EPUB: {str(e)}"
|
399
|
+
logger.error(error_msg)
|
400
|
+
raise RuntimeError(error_msg)
|
401
|
+
|
402
|
+
|
403
|
+
if __name__ == "__main__":
|
404
|
+
# Example usage with error handling
|
405
|
+
try:
|
406
|
+
tool = MarkdownToEpubTool()
|
407
|
+
result = tool.execute(
|
408
|
+
markdown_content="""
|
409
|
+
# My Book
|
410
|
+
|
411
|
+
## Chapter 1: Introduction
|
412
|
+
|
413
|
+
This is the first chapter with some **bold** text and a diagram:
|
414
|
+
|
415
|
+
```mermaid
|
416
|
+
graph TD
|
417
|
+
A[Start] --> B[Process]
|
418
|
+
B --> C[End]
|
419
|
+
```
|
420
|
+
|
421
|
+
---
|
422
|
+
|
423
|
+
## Chapter 2: Development
|
424
|
+
|
425
|
+
This is the second chapter with a code block:
|
426
|
+
|
427
|
+
```python
|
428
|
+
def hello():
|
429
|
+
print("Hello, World!")
|
430
|
+
```
|
431
|
+
""",
|
432
|
+
output_path="my_book.epub",
|
433
|
+
metadata='{"title": "My Book", "author": "John Doe"}',
|
434
|
+
style_config='{"theme": "light", "font_family": "Literata"}'
|
435
|
+
)
|
436
|
+
print(result)
|
437
|
+
except Exception as e:
|
438
|
+
print(f"Error: {e}")
|