quantalogic 0.33.4__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.
Files changed (107) hide show
  1. quantalogic/__init__.py +0 -4
  2. quantalogic/agent.py +603 -362
  3. quantalogic/agent_config.py +260 -28
  4. quantalogic/agent_factory.py +43 -17
  5. quantalogic/coding_agent.py +20 -12
  6. quantalogic/config.py +7 -4
  7. quantalogic/console_print_events.py +4 -8
  8. quantalogic/console_print_token.py +2 -2
  9. quantalogic/docs_cli.py +15 -10
  10. quantalogic/event_emitter.py +258 -83
  11. quantalogic/flow/__init__.py +23 -0
  12. quantalogic/flow/flow.py +595 -0
  13. quantalogic/flow/flow_extractor.py +672 -0
  14. quantalogic/flow/flow_generator.py +89 -0
  15. quantalogic/flow/flow_manager.py +407 -0
  16. quantalogic/flow/flow_manager_schema.py +169 -0
  17. quantalogic/flow/flow_yaml.md +419 -0
  18. quantalogic/generative_model.py +109 -77
  19. quantalogic/get_model_info.py +6 -6
  20. quantalogic/interactive_text_editor.py +100 -73
  21. quantalogic/main.py +36 -23
  22. quantalogic/model_info_list.py +12 -0
  23. quantalogic/model_info_litellm.py +14 -14
  24. quantalogic/prompts.py +2 -1
  25. quantalogic/{llm.py → quantlitellm.py} +29 -39
  26. quantalogic/search_agent.py +4 -4
  27. quantalogic/server/models.py +4 -1
  28. quantalogic/task_file_reader.py +5 -5
  29. quantalogic/task_runner.py +21 -20
  30. quantalogic/tool_manager.py +10 -21
  31. quantalogic/tools/__init__.py +98 -68
  32. quantalogic/tools/composio/composio.py +416 -0
  33. quantalogic/tools/{generate_database_report_tool.py → database/generate_database_report_tool.py} +4 -9
  34. quantalogic/tools/database/sql_query_tool_advanced.py +261 -0
  35. quantalogic/tools/document_tools/markdown_to_docx_tool.py +620 -0
  36. quantalogic/tools/document_tools/markdown_to_epub_tool.py +438 -0
  37. quantalogic/tools/document_tools/markdown_to_html_tool.py +362 -0
  38. quantalogic/tools/document_tools/markdown_to_ipynb_tool.py +319 -0
  39. quantalogic/tools/document_tools/markdown_to_latex_tool.py +420 -0
  40. quantalogic/tools/document_tools/markdown_to_pdf_tool.py +623 -0
  41. quantalogic/tools/document_tools/markdown_to_pptx_tool.py +319 -0
  42. quantalogic/tools/duckduckgo_search_tool.py +2 -4
  43. quantalogic/tools/finance/alpha_vantage_tool.py +440 -0
  44. quantalogic/tools/finance/ccxt_tool.py +373 -0
  45. quantalogic/tools/finance/finance_llm_tool.py +387 -0
  46. quantalogic/tools/finance/google_finance.py +192 -0
  47. quantalogic/tools/finance/market_intelligence_tool.py +520 -0
  48. quantalogic/tools/finance/technical_analysis_tool.py +491 -0
  49. quantalogic/tools/finance/tradingview_tool.py +336 -0
  50. quantalogic/tools/finance/yahoo_finance.py +236 -0
  51. quantalogic/tools/git/bitbucket_clone_repo_tool.py +181 -0
  52. quantalogic/tools/git/bitbucket_operations_tool.py +326 -0
  53. quantalogic/tools/git/clone_repo_tool.py +189 -0
  54. quantalogic/tools/git/git_operations_tool.py +532 -0
  55. quantalogic/tools/google_packages/google_news_tool.py +480 -0
  56. quantalogic/tools/grep_app_tool.py +123 -186
  57. quantalogic/tools/{dalle_e.py → image_generation/dalle_e.py} +37 -27
  58. quantalogic/tools/jinja_tool.py +6 -10
  59. quantalogic/tools/language_handlers/__init__.py +22 -9
  60. quantalogic/tools/list_directory_tool.py +131 -42
  61. quantalogic/tools/llm_tool.py +45 -15
  62. quantalogic/tools/llm_vision_tool.py +59 -7
  63. quantalogic/tools/markitdown_tool.py +17 -5
  64. quantalogic/tools/nasa_packages/models.py +47 -0
  65. quantalogic/tools/nasa_packages/nasa_apod_tool.py +232 -0
  66. quantalogic/tools/nasa_packages/nasa_neows_tool.py +147 -0
  67. quantalogic/tools/nasa_packages/services.py +82 -0
  68. quantalogic/tools/presentation_tools/presentation_llm_tool.py +396 -0
  69. quantalogic/tools/product_hunt/product_hunt_tool.py +258 -0
  70. quantalogic/tools/product_hunt/services.py +63 -0
  71. quantalogic/tools/rag_tool/__init__.py +48 -0
  72. quantalogic/tools/rag_tool/document_metadata.py +15 -0
  73. quantalogic/tools/rag_tool/query_response.py +20 -0
  74. quantalogic/tools/rag_tool/rag_tool.py +566 -0
  75. quantalogic/tools/rag_tool/rag_tool_beta.py +264 -0
  76. quantalogic/tools/read_html_tool.py +24 -38
  77. quantalogic/tools/replace_in_file_tool.py +10 -10
  78. quantalogic/tools/safe_python_interpreter_tool.py +10 -24
  79. quantalogic/tools/search_definition_names.py +2 -2
  80. quantalogic/tools/sequence_tool.py +14 -23
  81. quantalogic/tools/sql_query_tool.py +17 -19
  82. quantalogic/tools/tool.py +39 -15
  83. quantalogic/tools/unified_diff_tool.py +1 -1
  84. quantalogic/tools/utilities/csv_processor_tool.py +234 -0
  85. quantalogic/tools/utilities/download_file_tool.py +179 -0
  86. quantalogic/tools/utilities/mermaid_validator_tool.py +661 -0
  87. quantalogic/tools/utils/__init__.py +1 -4
  88. quantalogic/tools/utils/create_sample_database.py +24 -38
  89. quantalogic/tools/utils/generate_database_report.py +74 -82
  90. quantalogic/tools/wikipedia_search_tool.py +17 -21
  91. quantalogic/utils/ask_user_validation.py +1 -1
  92. quantalogic/utils/async_utils.py +35 -0
  93. quantalogic/utils/check_version.py +3 -5
  94. quantalogic/utils/get_all_models.py +2 -1
  95. quantalogic/utils/git_ls.py +21 -7
  96. quantalogic/utils/lm_studio_model_info.py +9 -7
  97. quantalogic/utils/python_interpreter.py +113 -43
  98. quantalogic/utils/xml_utility.py +178 -0
  99. quantalogic/version_check.py +1 -1
  100. quantalogic/welcome_message.py +7 -7
  101. quantalogic/xml_parser.py +0 -1
  102. {quantalogic-0.33.4.dist-info → quantalogic-0.40.0.dist-info}/METADATA +44 -1
  103. quantalogic-0.40.0.dist-info/RECORD +148 -0
  104. quantalogic-0.33.4.dist-info/RECORD +0 -102
  105. {quantalogic-0.33.4.dist-info → quantalogic-0.40.0.dist-info}/LICENSE +0 -0
  106. {quantalogic-0.33.4.dist-info → quantalogic-0.40.0.dist-info}/WHEEL +0 -0
  107. {quantalogic-0.33.4.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}")