quantalogic 0.35.0__py3-none-any.whl → 0.40.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.40.0.dist-info}/METADATA +41 -1
- quantalogic-0.40.0.dist-info/RECORD +148 -0
- quantalogic-0.35.0.dist-info/RECORD +0 -102
- {quantalogic-0.35.0.dist-info → quantalogic-0.40.0.dist-info}/LICENSE +0 -0
- {quantalogic-0.35.0.dist-info → quantalogic-0.40.0.dist-info}/WHEEL +0 -0
- {quantalogic-0.35.0.dist-info → quantalogic-0.40.0.dist-info}/entry_points.txt +0 -0
@@ -0,0 +1,362 @@
|
|
1
|
+
"""Tool for converting markdown content to well-structured HTML documents.
|
2
|
+
|
3
|
+
Why this tool:
|
4
|
+
- Provides a standardized way to convert markdown to professional HTML documents
|
5
|
+
- Supports custom themes and styling through CSS
|
6
|
+
- Handles complex elements like diagrams, code blocks, and tables
|
7
|
+
- Includes responsive design and modern web features
|
8
|
+
- Can be used as an intermediate format for other conversions
|
9
|
+
"""
|
10
|
+
|
11
|
+
import json
|
12
|
+
import os
|
13
|
+
from pathlib import Path
|
14
|
+
from typing import ClassVar, Dict, List, Optional
|
15
|
+
|
16
|
+
import markdown
|
17
|
+
from bs4 import BeautifulSoup
|
18
|
+
from loguru import logger
|
19
|
+
from pygments.formatters import HtmlFormatter
|
20
|
+
|
21
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
22
|
+
|
23
|
+
|
24
|
+
class MarkdownToHtmlTool(Tool):
|
25
|
+
"""Converts markdown to professional HTML documents with advanced formatting."""
|
26
|
+
|
27
|
+
model_config = {
|
28
|
+
"arbitrary_types_allowed": True
|
29
|
+
}
|
30
|
+
|
31
|
+
name: str = "markdown_to_html_tool"
|
32
|
+
description: str = (
|
33
|
+
"Converts markdown to HTML with support for images, Mermaid diagrams, "
|
34
|
+
"code blocks, tables, and advanced styling."
|
35
|
+
)
|
36
|
+
need_validation: bool = False
|
37
|
+
|
38
|
+
arguments: List[ToolArgument] = [
|
39
|
+
ToolArgument(
|
40
|
+
name="markdown_content",
|
41
|
+
arg_type="string",
|
42
|
+
description="Markdown content with support for Mermaid, images, code blocks, and tables",
|
43
|
+
required=True,
|
44
|
+
example="# Title\n\nContent with **bold** text\n\n```mermaid\ngraph TD\nA-->B\n```",
|
45
|
+
),
|
46
|
+
ToolArgument(
|
47
|
+
name="output_path",
|
48
|
+
arg_type="string",
|
49
|
+
description="Path for saving the HTML file",
|
50
|
+
required=True,
|
51
|
+
example="/path/to/output.html",
|
52
|
+
),
|
53
|
+
ToolArgument(
|
54
|
+
name="style_config",
|
55
|
+
arg_type="string",
|
56
|
+
description="JSON string with style settings (fonts, colors, sizes)",
|
57
|
+
required=False,
|
58
|
+
example='{"theme": "light", "font_family": "Roboto"}',
|
59
|
+
),
|
60
|
+
ToolArgument(
|
61
|
+
name="create_assets",
|
62
|
+
arg_type="boolean",
|
63
|
+
description="Create assets directory for styles and images",
|
64
|
+
required=False,
|
65
|
+
default="true",
|
66
|
+
),
|
67
|
+
ToolArgument(
|
68
|
+
name="template",
|
69
|
+
arg_type="string",
|
70
|
+
description="Optional HTML template path",
|
71
|
+
required=False,
|
72
|
+
example="path/to/template.html",
|
73
|
+
),
|
74
|
+
]
|
75
|
+
|
76
|
+
# Default style configuration
|
77
|
+
DEFAULT_STYLES: ClassVar[Dict[str, str]] = {
|
78
|
+
"theme": "light",
|
79
|
+
"font_family": "system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Ubuntu, sans-serif",
|
80
|
+
"code_font": "Consolas, 'Source Code Pro', monospace",
|
81
|
+
"primary_color": "#0070C0",
|
82
|
+
"background_color": "#ffffff",
|
83
|
+
"text_color": "#333333",
|
84
|
+
"link_color": "#0366d6",
|
85
|
+
"code_background": "#f6f8fa",
|
86
|
+
"border_color": "#e1e4e8",
|
87
|
+
"max_width": "900px",
|
88
|
+
}
|
89
|
+
|
90
|
+
# Default HTML template
|
91
|
+
DEFAULT_TEMPLATE: ClassVar[str] = """
|
92
|
+
<!DOCTYPE html>
|
93
|
+
<html lang="en">
|
94
|
+
<head>
|
95
|
+
<meta charset="UTF-8">
|
96
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
97
|
+
<title>{title}</title>
|
98
|
+
<style>{styles}</style>
|
99
|
+
{extra_head}
|
100
|
+
</head>
|
101
|
+
<body>
|
102
|
+
<div class="container">
|
103
|
+
{content}
|
104
|
+
</div>
|
105
|
+
{extra_body}
|
106
|
+
</body>
|
107
|
+
</html>
|
108
|
+
"""
|
109
|
+
|
110
|
+
def _normalize_path(self, path: str) -> Path:
|
111
|
+
"""Convert path string to normalized Path object."""
|
112
|
+
if path.startswith("~"):
|
113
|
+
path = os.path.expanduser(path)
|
114
|
+
return Path(path).resolve()
|
115
|
+
|
116
|
+
def _parse_style_config(self, style_config: Optional[str]) -> Dict[str, str]:
|
117
|
+
"""Parse and validate style configuration."""
|
118
|
+
try:
|
119
|
+
if not style_config:
|
120
|
+
return self.DEFAULT_STYLES.copy()
|
121
|
+
|
122
|
+
custom_styles = json.loads(style_config)
|
123
|
+
styles = self.DEFAULT_STYLES.copy()
|
124
|
+
styles.update(custom_styles)
|
125
|
+
return styles
|
126
|
+
except json.JSONDecodeError as e:
|
127
|
+
logger.error(f"Invalid style configuration JSON: {e}")
|
128
|
+
return self.DEFAULT_STYLES.copy()
|
129
|
+
|
130
|
+
def _generate_css(self, styles: Dict[str, str]) -> str:
|
131
|
+
"""Generate CSS based on style configuration."""
|
132
|
+
dark_theme = styles.get('theme', 'light') == 'dark'
|
133
|
+
if dark_theme:
|
134
|
+
styles.update({
|
135
|
+
'background_color': '#0d1117',
|
136
|
+
'text_color': '#c9d1d9',
|
137
|
+
'code_background': '#161b22',
|
138
|
+
'border_color': '#30363d',
|
139
|
+
})
|
140
|
+
|
141
|
+
css = f"""
|
142
|
+
:root {{
|
143
|
+
color-scheme: {styles['theme']};
|
144
|
+
}}
|
145
|
+
body {{
|
146
|
+
font-family: {styles['font_family']};
|
147
|
+
line-height: 1.6;
|
148
|
+
color: {styles['text_color']};
|
149
|
+
background-color: {styles['background_color']};
|
150
|
+
margin: 0;
|
151
|
+
padding: 0;
|
152
|
+
}}
|
153
|
+
.container {{
|
154
|
+
max-width: {styles['max_width']};
|
155
|
+
margin: 0 auto;
|
156
|
+
padding: 2rem;
|
157
|
+
}}
|
158
|
+
h1, h2, h3, h4, h5, h6 {{
|
159
|
+
color: {styles['primary_color']};
|
160
|
+
margin-top: 1.5em;
|
161
|
+
margin-bottom: 0.5em;
|
162
|
+
font-weight: 600;
|
163
|
+
}}
|
164
|
+
a {{
|
165
|
+
color: {styles['link_color']};
|
166
|
+
text-decoration: none;
|
167
|
+
}}
|
168
|
+
a:hover {{
|
169
|
+
text-decoration: underline;
|
170
|
+
}}
|
171
|
+
pre, code {{
|
172
|
+
font-family: {styles['code_font']};
|
173
|
+
background-color: {styles['code_background']};
|
174
|
+
border-radius: 6px;
|
175
|
+
}}
|
176
|
+
pre {{
|
177
|
+
padding: 1rem;
|
178
|
+
overflow-x: auto;
|
179
|
+
}}
|
180
|
+
code {{
|
181
|
+
padding: 0.2em 0.4em;
|
182
|
+
}}
|
183
|
+
pre code {{
|
184
|
+
padding: 0;
|
185
|
+
}}
|
186
|
+
img {{
|
187
|
+
max-width: 100%;
|
188
|
+
height: auto;
|
189
|
+
border-radius: 6px;
|
190
|
+
}}
|
191
|
+
table {{
|
192
|
+
width: 100%;
|
193
|
+
border-collapse: collapse;
|
194
|
+
margin: 1em 0;
|
195
|
+
}}
|
196
|
+
th, td {{
|
197
|
+
border: 1px solid {styles['border_color']};
|
198
|
+
padding: 8px;
|
199
|
+
text-align: left;
|
200
|
+
}}
|
201
|
+
th {{
|
202
|
+
background-color: {styles['code_background']};
|
203
|
+
}}
|
204
|
+
blockquote {{
|
205
|
+
margin: 1em 0;
|
206
|
+
padding-left: 1em;
|
207
|
+
border-left: 4px solid {styles['primary_color']};
|
208
|
+
color: {styles['text_color']};
|
209
|
+
}}
|
210
|
+
hr {{
|
211
|
+
border: none;
|
212
|
+
border-top: 1px solid {styles['border_color']};
|
213
|
+
margin: 2em 0;
|
214
|
+
}}
|
215
|
+
.mermaid {{
|
216
|
+
text-align: center;
|
217
|
+
}}
|
218
|
+
@media (max-width: 768px) {{
|
219
|
+
.container {{
|
220
|
+
padding: 1rem;
|
221
|
+
}}
|
222
|
+
}}
|
223
|
+
"""
|
224
|
+
|
225
|
+
return css + HtmlFormatter().get_style_defs('.highlight')
|
226
|
+
|
227
|
+
def _process_mermaid_diagrams(self, html_content: str) -> str:
|
228
|
+
"""Convert Mermaid diagram code blocks to rendered diagrams."""
|
229
|
+
soup = BeautifulSoup(html_content, 'html.parser')
|
230
|
+
mermaid_blocks = soup.find_all('code', class_='language-mermaid')
|
231
|
+
|
232
|
+
for block in mermaid_blocks:
|
233
|
+
try:
|
234
|
+
diagram_div = soup.new_tag('div')
|
235
|
+
diagram_div['class'] = 'mermaid'
|
236
|
+
diagram_div.string = block.text
|
237
|
+
block.parent.replace_with(diagram_div)
|
238
|
+
except Exception as e:
|
239
|
+
logger.error(f"Error processing Mermaid diagram: {e}")
|
240
|
+
|
241
|
+
return str(soup)
|
242
|
+
|
243
|
+
def _setup_assets_directory(self, output_path: Path) -> Path:
|
244
|
+
"""Create and setup assets directory for styles and images."""
|
245
|
+
assets_dir = output_path.parent / 'assets'
|
246
|
+
assets_dir.mkdir(parents=True, exist_ok=True)
|
247
|
+
return assets_dir
|
248
|
+
|
249
|
+
def execute(self, **kwargs) -> str:
|
250
|
+
"""Execute the markdown to HTML conversion.
|
251
|
+
|
252
|
+
Args:
|
253
|
+
**kwargs: Tool arguments including markdown_content, output_path,
|
254
|
+
style_config, create_assets, and template
|
255
|
+
|
256
|
+
Returns:
|
257
|
+
Success message with output path
|
258
|
+
"""
|
259
|
+
try:
|
260
|
+
markdown_content = kwargs['markdown_content']
|
261
|
+
output_path = self._normalize_path(kwargs['output_path'])
|
262
|
+
style_config = kwargs.get('style_config')
|
263
|
+
create_assets = kwargs.get('create_assets', True)
|
264
|
+
template_path = kwargs.get('template')
|
265
|
+
|
266
|
+
# Create output directory
|
267
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
268
|
+
|
269
|
+
# Setup assets if needed
|
270
|
+
assets_dir = self._setup_assets_directory(output_path) if create_assets else None
|
271
|
+
|
272
|
+
# Convert markdown to HTML
|
273
|
+
html_content = markdown.markdown(
|
274
|
+
markdown_content,
|
275
|
+
extensions=[
|
276
|
+
'extra',
|
277
|
+
'codehilite',
|
278
|
+
'tables',
|
279
|
+
'fenced_code',
|
280
|
+
'toc',
|
281
|
+
'sane_lists',
|
282
|
+
]
|
283
|
+
)
|
284
|
+
|
285
|
+
# Process Mermaid diagrams
|
286
|
+
html_content = self._process_mermaid_diagrams(html_content)
|
287
|
+
|
288
|
+
# Generate styles
|
289
|
+
styles = self._parse_style_config(style_config)
|
290
|
+
css = self._generate_css(styles)
|
291
|
+
|
292
|
+
# Load custom template if provided
|
293
|
+
template = self.DEFAULT_TEMPLATE
|
294
|
+
if template_path:
|
295
|
+
try:
|
296
|
+
template_path = self._normalize_path(template_path)
|
297
|
+
with open(template_path, 'r') as f:
|
298
|
+
template = f.read()
|
299
|
+
except Exception as e:
|
300
|
+
logger.error(f"Error loading template: {e}")
|
301
|
+
|
302
|
+
# Extract title from content
|
303
|
+
soup = BeautifulSoup(html_content, 'html.parser')
|
304
|
+
title = soup.find('h1')
|
305
|
+
title = title.text if title else 'Document'
|
306
|
+
|
307
|
+
# Add Mermaid support
|
308
|
+
extra_head = '''
|
309
|
+
<script src="https://cdn.jsdelivr.net/npm/mermaid/dist/mermaid.min.js"></script>
|
310
|
+
<script>
|
311
|
+
document.addEventListener('DOMContentLoaded', function() {
|
312
|
+
mermaid.initialize({startOnLoad: true});
|
313
|
+
});
|
314
|
+
</script>
|
315
|
+
'''
|
316
|
+
|
317
|
+
# Generate final HTML
|
318
|
+
final_html = template.format(
|
319
|
+
title=title,
|
320
|
+
styles=css,
|
321
|
+
content=html_content,
|
322
|
+
extra_head=extra_head,
|
323
|
+
extra_body=''
|
324
|
+
)
|
325
|
+
|
326
|
+
# Write output file
|
327
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
328
|
+
f.write(final_html)
|
329
|
+
|
330
|
+
return f"Successfully created HTML at: {output_path}"
|
331
|
+
|
332
|
+
except Exception as e:
|
333
|
+
error_msg = f"Error converting markdown to HTML: {str(e)}"
|
334
|
+
logger.error(error_msg)
|
335
|
+
raise RuntimeError(error_msg)
|
336
|
+
|
337
|
+
|
338
|
+
if __name__ == "__main__":
|
339
|
+
# Example usage with error handling
|
340
|
+
try:
|
341
|
+
tool = MarkdownToHtmlTool()
|
342
|
+
result = tool.execute(
|
343
|
+
markdown_content="""
|
344
|
+
# Sample Document
|
345
|
+
|
346
|
+
## Features
|
347
|
+
- Modern, responsive design
|
348
|
+
- Syntax highlighting
|
349
|
+
- Mermaid diagrams
|
350
|
+
|
351
|
+
```mermaid
|
352
|
+
graph TD
|
353
|
+
A[Start] --> B[Process]
|
354
|
+
B --> C[End]
|
355
|
+
```
|
356
|
+
""",
|
357
|
+
output_path="test_output.html",
|
358
|
+
style_config='{"theme": "dark"}'
|
359
|
+
)
|
360
|
+
print(result)
|
361
|
+
except Exception as e:
|
362
|
+
print(f"Error: {e}")
|
@@ -0,0 +1,319 @@
|
|
1
|
+
"""Tool for converting markdown content to Jupyter Notebook format.
|
2
|
+
|
3
|
+
Why this tool:
|
4
|
+
- Provides a standardized way to convert markdown to interactive notebooks
|
5
|
+
- Creates executable code cells from code blocks
|
6
|
+
- Supports rich media and interactive elements
|
7
|
+
- Maintains metadata and cell execution order
|
8
|
+
- Perfect for tutorials, documentation, and educational content
|
9
|
+
"""
|
10
|
+
|
11
|
+
import json
|
12
|
+
import os
|
13
|
+
import re
|
14
|
+
from pathlib import Path
|
15
|
+
from typing import Any, Dict, List, Optional
|
16
|
+
|
17
|
+
import mermaid
|
18
|
+
import nbformat
|
19
|
+
from loguru import logger
|
20
|
+
from nbformat.v4 import new_code_cell, new_markdown_cell, new_notebook
|
21
|
+
|
22
|
+
from quantalogic.tools.tool import Tool, ToolArgument
|
23
|
+
|
24
|
+
|
25
|
+
class MarkdownToIpynbTool(Tool):
|
26
|
+
"""Converts markdown to Jupyter Notebooks with interactive elements."""
|
27
|
+
|
28
|
+
name: str = "markdown_to_ipynb_tool"
|
29
|
+
description: str = (
|
30
|
+
"Converts markdown to Jupyter Notebook format with support for "
|
31
|
+
"executable code cells, rich media, and interactive elements."
|
32
|
+
)
|
33
|
+
need_validation: bool = False
|
34
|
+
|
35
|
+
arguments: list = [
|
36
|
+
ToolArgument(
|
37
|
+
name="markdown_content",
|
38
|
+
arg_type="string",
|
39
|
+
description="Markdown content with code blocks and optional cell metadata",
|
40
|
+
required=True,
|
41
|
+
example='# Title\n\nText\n\n```python\nprint("Hello")\n```',
|
42
|
+
),
|
43
|
+
ToolArgument(
|
44
|
+
name="output_path",
|
45
|
+
arg_type="string",
|
46
|
+
description="Path for saving the notebook file",
|
47
|
+
required=True,
|
48
|
+
example="/path/to/output.ipynb",
|
49
|
+
),
|
50
|
+
ToolArgument(
|
51
|
+
name="kernel_name",
|
52
|
+
arg_type="string",
|
53
|
+
description="Jupyter kernel name (e.g., python3, ir)",
|
54
|
+
required=False,
|
55
|
+
default="python3",
|
56
|
+
),
|
57
|
+
ToolArgument(
|
58
|
+
name="metadata",
|
59
|
+
arg_type="string",
|
60
|
+
description="JSON string with notebook metadata",
|
61
|
+
required=False,
|
62
|
+
example='{"authors": ["John Doe"], "license": "MIT"}',
|
63
|
+
),
|
64
|
+
]
|
65
|
+
|
66
|
+
# Default notebook metadata
|
67
|
+
DEFAULT_METADATA: Dict[str, Any] = {
|
68
|
+
"kernelspec": {
|
69
|
+
"display_name": "Python 3",
|
70
|
+
"language": "python",
|
71
|
+
"name": "python3"
|
72
|
+
},
|
73
|
+
"language_info": {
|
74
|
+
"codemirror_mode": {
|
75
|
+
"name": "ipython",
|
76
|
+
"version": 3
|
77
|
+
},
|
78
|
+
"file_extension": ".py",
|
79
|
+
"mimetype": "text/x-python",
|
80
|
+
"name": "python",
|
81
|
+
"nbconvert_exporter": "python",
|
82
|
+
"pygments_lexer": "ipython3",
|
83
|
+
"version": "3.8.0"
|
84
|
+
}
|
85
|
+
}
|
86
|
+
|
87
|
+
def _normalize_path(self, path: str) -> Path:
|
88
|
+
"""Convert path string to normalized Path object."""
|
89
|
+
if path.startswith("~"):
|
90
|
+
path = os.path.expanduser(path)
|
91
|
+
return Path(path).resolve()
|
92
|
+
|
93
|
+
def _parse_metadata(self, metadata: Optional[str]) -> Dict[str, Any]:
|
94
|
+
"""Parse and validate notebook metadata."""
|
95
|
+
try:
|
96
|
+
if not metadata:
|
97
|
+
return self.DEFAULT_METADATA.copy()
|
98
|
+
|
99
|
+
custom_metadata = json.loads(metadata)
|
100
|
+
metadata = self.DEFAULT_METADATA.copy()
|
101
|
+
metadata.update(custom_metadata)
|
102
|
+
return metadata
|
103
|
+
except json.JSONDecodeError as e:
|
104
|
+
logger.error(f"Invalid metadata JSON: {e}")
|
105
|
+
return self.DEFAULT_METADATA.copy()
|
106
|
+
|
107
|
+
def _process_mermaid_diagrams(self, content: str) -> str:
|
108
|
+
"""Convert Mermaid diagram code blocks to embedded SVG."""
|
109
|
+
def replace_mermaid(match):
|
110
|
+
try:
|
111
|
+
diagram = mermaid.generate_diagram(match.group(1))
|
112
|
+
return f"```html\n<div class='mermaid-diagram'>\n{diagram}\n</div>\n```"
|
113
|
+
except Exception as e:
|
114
|
+
logger.error(f"Error processing Mermaid diagram: {e}")
|
115
|
+
return match.group(0)
|
116
|
+
|
117
|
+
pattern = r'```mermaid\n(.*?)\n```'
|
118
|
+
return re.sub(pattern, replace_mermaid, content, flags=re.DOTALL)
|
119
|
+
|
120
|
+
def _extract_cell_metadata(self, content: str) -> Dict[str, Any]:
|
121
|
+
"""Extract cell metadata from special comments."""
|
122
|
+
metadata = {}
|
123
|
+
lines = content.split('\n')
|
124
|
+
|
125
|
+
# Look for metadata in comments at the start of the cell
|
126
|
+
for line in lines:
|
127
|
+
if line.startswith('# @'):
|
128
|
+
try:
|
129
|
+
key, value = line[3:].split(':', 1)
|
130
|
+
metadata[key.strip()] = json.loads(value.strip())
|
131
|
+
except Exception:
|
132
|
+
continue
|
133
|
+
else:
|
134
|
+
break
|
135
|
+
|
136
|
+
return metadata
|
137
|
+
|
138
|
+
def _split_into_cells(self, content: str) -> List[Dict[str, Any]]:
|
139
|
+
"""Split markdown content into notebook cells."""
|
140
|
+
cells = []
|
141
|
+
current_cell = []
|
142
|
+
in_code_block = False
|
143
|
+
code_language = None
|
144
|
+
|
145
|
+
lines = content.split('\n')
|
146
|
+
i = 0
|
147
|
+
|
148
|
+
while i < len(lines):
|
149
|
+
line = lines[i]
|
150
|
+
|
151
|
+
# Check for code block start
|
152
|
+
if line.startswith('```'):
|
153
|
+
if not in_code_block:
|
154
|
+
# If we have accumulated markdown content, save it
|
155
|
+
if current_cell:
|
156
|
+
cells.append({
|
157
|
+
'type': 'markdown',
|
158
|
+
'content': '\n'.join(current_cell)
|
159
|
+
})
|
160
|
+
current_cell = []
|
161
|
+
|
162
|
+
# Start new code block
|
163
|
+
in_code_block = True
|
164
|
+
code_language = line[3:].strip()
|
165
|
+
current_cell = []
|
166
|
+
i += 1
|
167
|
+
|
168
|
+
# Look ahead for metadata comments
|
169
|
+
code_metadata = {}
|
170
|
+
while i < len(lines) and lines[i].startswith('# @'):
|
171
|
+
try:
|
172
|
+
key, value = lines[i][3:].split(':', 1)
|
173
|
+
code_metadata[key.strip()] = json.loads(value.strip())
|
174
|
+
except Exception:
|
175
|
+
current_cell.append(lines[i])
|
176
|
+
i += 1
|
177
|
+
continue
|
178
|
+
else:
|
179
|
+
# End of code block
|
180
|
+
in_code_block = False
|
181
|
+
cells.append({
|
182
|
+
'type': 'code',
|
183
|
+
'content': '\n'.join(current_cell),
|
184
|
+
'language': code_language,
|
185
|
+
'metadata': code_metadata if 'code_metadata' in locals() else {}
|
186
|
+
})
|
187
|
+
current_cell = []
|
188
|
+
code_metadata = {}
|
189
|
+
else:
|
190
|
+
current_cell.append(line)
|
191
|
+
|
192
|
+
i += 1
|
193
|
+
|
194
|
+
# Add any remaining content
|
195
|
+
if current_cell:
|
196
|
+
cells.append({
|
197
|
+
'type': 'markdown' if not in_code_block else 'code',
|
198
|
+
'content': '\n'.join(current_cell),
|
199
|
+
'language': code_language if in_code_block else None,
|
200
|
+
'metadata': code_metadata if in_code_block and 'code_metadata' in locals() else {}
|
201
|
+
})
|
202
|
+
|
203
|
+
return cells
|
204
|
+
|
205
|
+
def execute(self, **kwargs) -> str:
|
206
|
+
"""Execute the markdown to Jupyter Notebook conversion.
|
207
|
+
|
208
|
+
Args:
|
209
|
+
**kwargs: Tool arguments including markdown_content, output_path,
|
210
|
+
kernel_name, and metadata
|
211
|
+
|
212
|
+
Returns:
|
213
|
+
Success message with output path
|
214
|
+
"""
|
215
|
+
try:
|
216
|
+
markdown_content = kwargs['markdown_content']
|
217
|
+
output_path = self._normalize_path(kwargs['output_path'])
|
218
|
+
kernel_name = kwargs.get('kernel_name', 'python3')
|
219
|
+
metadata_str = kwargs.get('metadata')
|
220
|
+
|
221
|
+
# Create output directory
|
222
|
+
output_path.parent.mkdir(parents=True, exist_ok=True)
|
223
|
+
|
224
|
+
# Process Mermaid diagrams
|
225
|
+
content = self._process_mermaid_diagrams(markdown_content)
|
226
|
+
|
227
|
+
# Split content into cells
|
228
|
+
cells = self._split_into_cells(content)
|
229
|
+
|
230
|
+
# Create notebook
|
231
|
+
nb = new_notebook()
|
232
|
+
|
233
|
+
# Set notebook metadata
|
234
|
+
metadata = self._parse_metadata(metadata_str)
|
235
|
+
metadata['kernelspec']['name'] = kernel_name
|
236
|
+
metadata['kernelspec']['display_name'] = kernel_name.capitalize()
|
237
|
+
nb.metadata = metadata
|
238
|
+
|
239
|
+
# Add cells to notebook
|
240
|
+
for cell in cells:
|
241
|
+
if cell['type'] == 'markdown':
|
242
|
+
nb.cells.append(new_markdown_cell(
|
243
|
+
cell['content']
|
244
|
+
))
|
245
|
+
else:
|
246
|
+
# Create code cell with metadata
|
247
|
+
code_cell = new_code_cell(
|
248
|
+
cell['content'],
|
249
|
+
metadata=cell['metadata']
|
250
|
+
)
|
251
|
+
nb.cells.append(code_cell)
|
252
|
+
|
253
|
+
# Write notebook file
|
254
|
+
with open(output_path, 'w', encoding='utf-8') as f:
|
255
|
+
nbformat.write(nb, f)
|
256
|
+
|
257
|
+
return f"Successfully created Jupyter Notebook at: {output_path}"
|
258
|
+
|
259
|
+
except Exception as e:
|
260
|
+
error_msg = f"Error converting markdown to notebook: {str(e)}"
|
261
|
+
logger.error(error_msg)
|
262
|
+
raise RuntimeError(error_msg)
|
263
|
+
|
264
|
+
|
265
|
+
if __name__ == "__main__":
|
266
|
+
# Example usage with error handling
|
267
|
+
try:
|
268
|
+
tool = MarkdownToIpynbTool()
|
269
|
+
result = tool.execute(
|
270
|
+
markdown_content="""
|
271
|
+
# Interactive Python Tutorial
|
272
|
+
|
273
|
+
This notebook demonstrates various Python concepts.
|
274
|
+
|
275
|
+
## Basic Operations
|
276
|
+
|
277
|
+
Let's start with a simple calculation:
|
278
|
+
|
279
|
+
```python
|
280
|
+
# @tags: ["basic-math"]
|
281
|
+
# Calculate the sum of numbers
|
282
|
+
result = sum(range(10))
|
283
|
+
print(f"Sum: {result}")
|
284
|
+
```
|
285
|
+
|
286
|
+
## Data Visualization
|
287
|
+
|
288
|
+
Now let's create a simple plot:
|
289
|
+
|
290
|
+
```python
|
291
|
+
# @tags: ["visualization"]
|
292
|
+
import matplotlib.pyplot as plt
|
293
|
+
import numpy as np
|
294
|
+
|
295
|
+
x = np.linspace(0, 10, 100)
|
296
|
+
y = np.sin(x)
|
297
|
+
|
298
|
+
plt.plot(x, y)
|
299
|
+
plt.title("Sine Wave")
|
300
|
+
plt.show()
|
301
|
+
```
|
302
|
+
|
303
|
+
## System Architecture
|
304
|
+
|
305
|
+
Here's a diagram of our system:
|
306
|
+
|
307
|
+
```mermaid
|
308
|
+
graph TD
|
309
|
+
A[Input] --> B[Process]
|
310
|
+
B --> C[Output]
|
311
|
+
B --> D[Log]
|
312
|
+
```
|
313
|
+
""",
|
314
|
+
output_path="tutorial.ipynb",
|
315
|
+
metadata='{"authors": ["Jane Doe"], "description": "Interactive Python Tutorial"}'
|
316
|
+
)
|
317
|
+
print(result)
|
318
|
+
except Exception as e:
|
319
|
+
print(f"Error: {e}")
|