fariasultana commited on
Commit
56beda7
·
verified ·
1 Parent(s): 00ecf49

feat: Add capabilities/templates.py

Browse files
Files changed (1) hide show
  1. capabilities/templates.py +614 -0
capabilities/templates.py ADDED
@@ -0,0 +1,614 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Jinja Templates and MDX Components for MiniMind Max2
3
+ Enhanced code generation with rich formatting support.
4
+ """
5
+
6
+ from dataclasses import dataclass, field
7
+ from typing import List, Optional, Dict, Any, Union
8
+ import re
9
+ import json
10
+ from enum import Enum
11
+
12
+
13
+ class MDXComponentType(Enum):
14
+ """Supported MDX component types."""
15
+ LINEAR_PROCESS_FLOW = "LinearProcessFlow"
16
+ QUIZ = "Quiz"
17
+ MATH = "math"
18
+ THINKING = "Thinking"
19
+ CODE_BLOCK = "CodeBlock"
20
+ MERMAID = "Mermaid"
21
+ CALLOUT = "Callout"
22
+ TABS = "Tabs"
23
+ ACCORDION = "Accordion"
24
+
25
+
26
+ @dataclass
27
+ class CodeBlockMeta:
28
+ """Metadata for extended code blocks."""
29
+ project: str = ""
30
+ file: str = ""
31
+ block_type: str = "code" # react, nodejs, html, markdown, diagram, code
32
+ language: str = "python"
33
+ title: str = ""
34
+ show_line_numbers: bool = True
35
+ highlight_lines: List[int] = field(default_factory=list)
36
+ executable: bool = False
37
+
38
+
39
+ @dataclass
40
+ class ThinkingBlock:
41
+ """Represents a thinking/reasoning block."""
42
+ content: str
43
+ step: int = 0
44
+ is_final: bool = False
45
+ confidence: float = 1.0
46
+
47
+
48
+ class JinjaTemplateEngine:
49
+ """
50
+ Jinja-style template engine for code generation.
51
+ Supports variables, loops, conditionals, and filters.
52
+ """
53
+
54
+ def __init__(self):
55
+ self.filters = {
56
+ "upper": str.upper,
57
+ "lower": str.lower,
58
+ "capitalize": str.capitalize,
59
+ "title": str.title,
60
+ "strip": str.strip,
61
+ "length": len,
62
+ "default": lambda x, d: x if x else d,
63
+ "join": lambda x, sep=", ": sep.join(str(i) for i in x),
64
+ "first": lambda x: x[0] if x else None,
65
+ "last": lambda x: x[-1] if x else None,
66
+ "json": json.dumps,
67
+ }
68
+ self.globals = {}
69
+
70
+ def register_filter(self, name: str, func):
71
+ """Register a custom filter."""
72
+ self.filters[name] = func
73
+
74
+ def register_global(self, name: str, value):
75
+ """Register a global variable."""
76
+ self.globals[name] = value
77
+
78
+ def render(self, template: str, context: Dict[str, Any]) -> str:
79
+ """
80
+ Render a Jinja-style template.
81
+
82
+ Args:
83
+ template: Template string with {{ }} and {% %} blocks
84
+ context: Variables to substitute
85
+
86
+ Returns:
87
+ Rendered string
88
+ """
89
+ # Merge context with globals
90
+ full_context = {**self.globals, **context}
91
+
92
+ # Process control structures first
93
+ result = self._process_control_structures(template, full_context)
94
+
95
+ # Then substitute variables
96
+ result = self._substitute_variables(result, full_context)
97
+
98
+ return result
99
+
100
+ def _substitute_variables(self, template: str, context: Dict[str, Any]) -> str:
101
+ """Substitute {{ variable }} expressions."""
102
+ pattern = r'\{\{\s*(.+?)\s*\}\}'
103
+
104
+ def replace(match):
105
+ expr = match.group(1)
106
+ return str(self._evaluate_expression(expr, context))
107
+
108
+ return re.sub(pattern, replace, template)
109
+
110
+ def _evaluate_expression(self, expr: str, context: Dict[str, Any]) -> Any:
111
+ """Evaluate a Jinja expression."""
112
+ # Handle filters (e.g., name|upper)
113
+ if '|' in expr:
114
+ parts = expr.split('|')
115
+ value = self._evaluate_expression(parts[0].strip(), context)
116
+ for filter_expr in parts[1:]:
117
+ filter_expr = filter_expr.strip()
118
+ # Handle filter with arguments
119
+ if '(' in filter_expr:
120
+ filter_name = filter_expr[:filter_expr.index('(')]
121
+ args_str = filter_expr[filter_expr.index('(')+1:-1]
122
+ if filter_name in self.filters:
123
+ value = self.filters[filter_name](value, args_str.strip('"\''))
124
+ else:
125
+ if filter_expr in self.filters:
126
+ value = self.filters[filter_expr](value)
127
+ return value
128
+
129
+ # Handle attribute access (e.g., user.name)
130
+ if '.' in expr:
131
+ parts = expr.split('.')
132
+ value = context.get(parts[0])
133
+ for part in parts[1:]:
134
+ if isinstance(value, dict):
135
+ value = value.get(part)
136
+ elif hasattr(value, part):
137
+ value = getattr(value, part)
138
+ else:
139
+ return None
140
+ return value
141
+
142
+ # Handle index access (e.g., items[0])
143
+ if '[' in expr:
144
+ match = re.match(r'(\w+)\[(.+)\]', expr)
145
+ if match:
146
+ var_name = match.group(1)
147
+ index = match.group(2)
148
+ value = context.get(var_name)
149
+ if value:
150
+ try:
151
+ idx = int(index)
152
+ return value[idx]
153
+ except (ValueError, IndexError):
154
+ return None
155
+ return None
156
+
157
+ # Simple variable lookup
158
+ return context.get(expr, '')
159
+
160
+ def _process_control_structures(self, template: str, context: Dict[str, Any]) -> str:
161
+ """Process {% for %}, {% if %}, etc."""
162
+ result = template
163
+
164
+ # Process for loops
165
+ for_pattern = r'\{%\s*for\s+(\w+)\s+in\s+(\w+)\s*%\}(.*?)\{%\s*endfor\s*%\}'
166
+ while True:
167
+ match = re.search(for_pattern, result, re.DOTALL)
168
+ if not match:
169
+ break
170
+
171
+ var_name = match.group(1)
172
+ iterable_name = match.group(2)
173
+ body = match.group(3)
174
+
175
+ iterable = context.get(iterable_name, [])
176
+ rendered_parts = []
177
+
178
+ for item in iterable:
179
+ item_context = {**context, var_name: item}
180
+ rendered_parts.append(self._substitute_variables(body, item_context))
181
+
182
+ result = result[:match.start()] + ''.join(rendered_parts) + result[match.end():]
183
+
184
+ # Process if statements
185
+ if_pattern = r'\{%\s*if\s+(.+?)\s*%\}(.*?)(?:\{%\s*else\s*%\}(.*?))?\{%\s*endif\s*%\}'
186
+ while True:
187
+ match = re.search(if_pattern, result, re.DOTALL)
188
+ if not match:
189
+ break
190
+
191
+ condition = match.group(1)
192
+ true_block = match.group(2)
193
+ false_block = match.group(3) or ''
194
+
195
+ if self._evaluate_condition(condition, context):
196
+ replacement = true_block
197
+ else:
198
+ replacement = false_block
199
+
200
+ result = result[:match.start()] + replacement + result[match.end():]
201
+
202
+ return result
203
+
204
+ def _evaluate_condition(self, condition: str, context: Dict[str, Any]) -> bool:
205
+ """Evaluate a condition expression."""
206
+ # Simple conditions
207
+ if ' == ' in condition:
208
+ left, right = condition.split(' == ', 1)
209
+ left_val = self._evaluate_expression(left.strip(), context)
210
+ right_val = right.strip().strip('"\'')
211
+ return str(left_val) == right_val
212
+
213
+ if ' != ' in condition:
214
+ left, right = condition.split(' != ', 1)
215
+ left_val = self._evaluate_expression(left.strip(), context)
216
+ right_val = right.strip().strip('"\'')
217
+ return str(left_val) != right_val
218
+
219
+ if ' > ' in condition:
220
+ left, right = condition.split(' > ', 1)
221
+ left_val = self._evaluate_expression(left.strip(), context)
222
+ right_val = float(right.strip())
223
+ return float(left_val) > right_val
224
+
225
+ if ' < ' in condition:
226
+ left, right = condition.split(' < ', 1)
227
+ left_val = self._evaluate_expression(left.strip(), context)
228
+ right_val = float(right.strip())
229
+ return float(left_val) < right_val
230
+
231
+ # Truthiness check
232
+ value = self._evaluate_expression(condition.strip(), context)
233
+ return bool(value)
234
+
235
+
236
+ class MDXRenderer:
237
+ """
238
+ Render MDX components for rich documentation and UI.
239
+ """
240
+
241
+ def __init__(self):
242
+ self.components = {}
243
+ self._register_default_components()
244
+
245
+ def _register_default_components(self):
246
+ """Register default MDX components."""
247
+ self.components = {
248
+ MDXComponentType.LINEAR_PROCESS_FLOW: self._render_linear_process_flow,
249
+ MDXComponentType.QUIZ: self._render_quiz,
250
+ MDXComponentType.MATH: self._render_math,
251
+ MDXComponentType.THINKING: self._render_thinking,
252
+ MDXComponentType.MERMAID: self._render_mermaid,
253
+ MDXComponentType.CALLOUT: self._render_callout,
254
+ MDXComponentType.TABS: self._render_tabs,
255
+ }
256
+
257
+ def render(self, component_type: MDXComponentType, props: Dict[str, Any]) -> str:
258
+ """Render an MDX component."""
259
+ if component_type in self.components:
260
+ return self.components[component_type](props)
261
+ return f"<!-- Unknown component: {component_type} -->"
262
+
263
+ def _render_linear_process_flow(self, props: Dict[str, Any]) -> str:
264
+ """Render a linear process flow diagram."""
265
+ steps = props.get("steps", [])
266
+ title = props.get("title", "Process Flow")
267
+
268
+ html = f'<div class="linear-process-flow">\n'
269
+ html += f' <h3>{title}</h3>\n'
270
+ html += ' <div class="steps">\n'
271
+
272
+ for i, step in enumerate(steps):
273
+ html += f' <div class="step" data-step="{i+1}">\n'
274
+ html += f' <div class="step-number">{i+1}</div>\n'
275
+ html += f' <div class="step-content">\n'
276
+ html += f' <h4>{step.get("title", f"Step {i+1}")}</h4>\n'
277
+ html += f' <p>{step.get("description", "")}</p>\n'
278
+ html += ' </div>\n'
279
+ if i < len(steps) - 1:
280
+ html += ' <div class="step-arrow">→</div>\n'
281
+ html += ' </div>\n'
282
+
283
+ html += ' </div>\n'
284
+ html += '</div>'
285
+
286
+ return html
287
+
288
+ def _render_quiz(self, props: Dict[str, Any]) -> str:
289
+ """Render an interactive quiz."""
290
+ questions = props.get("questions", [])
291
+ title = props.get("title", "Quiz")
292
+
293
+ html = f'<div class="quiz" data-quiz-id="{props.get("id", "quiz")}">\n'
294
+ html += f' <h3>{title}</h3>\n'
295
+
296
+ for i, q in enumerate(questions):
297
+ html += f' <div class="question" data-question="{i}">\n'
298
+ html += f' <p class="question-text">{q.get("question", "")}</p>\n'
299
+ html += ' <div class="options">\n'
300
+
301
+ for j, opt in enumerate(q.get("options", [])):
302
+ correct = j == q.get("correct", 0)
303
+ html += f' <label class="option" data-correct="{str(correct).lower()}">\n'
304
+ html += f' <input type="radio" name="q{i}" value="{j}">\n'
305
+ html += f' <span>{opt}</span>\n'
306
+ html += ' </label>\n'
307
+
308
+ html += ' </div>\n'
309
+ if q.get("explanation"):
310
+ html += f' <div class="explanation" hidden>{q["explanation"]}</div>\n'
311
+ html += ' </div>\n'
312
+
313
+ html += '</div>'
314
+ return html
315
+
316
+ def _render_math(self, props: Dict[str, Any]) -> str:
317
+ """Render LaTeX math expressions."""
318
+ expression = props.get("expression", "")
319
+ display = props.get("display", False)
320
+
321
+ if display:
322
+ return f'$${expression}$$'
323
+ return f'${expression}$'
324
+
325
+ def _render_thinking(self, props: Dict[str, Any]) -> str:
326
+ """Render a thinking/reasoning block."""
327
+ content = props.get("content", "")
328
+ step = props.get("step", 0)
329
+ is_visible = props.get("visible", False)
330
+
331
+ css_class = "thinking-block" + (" visible" if is_visible else " hidden")
332
+
333
+ html = f'<div class="{css_class}" data-step="{step}">\n'
334
+ html += ' <div class="thinking-header">\n'
335
+ html += f' <span class="thinking-icon">🤔</span>\n'
336
+ html += f' <span class="thinking-label">Thinking (Step {step})</span>\n'
337
+ html += ' </div>\n'
338
+ html += f' <div class="thinking-content">{content}</div>\n'
339
+ html += '</div>'
340
+
341
+ return html
342
+
343
+ def _render_mermaid(self, props: Dict[str, Any]) -> str:
344
+ """Render a Mermaid diagram."""
345
+ code = props.get("code", "")
346
+ title = props.get("title", "")
347
+
348
+ html = '<div class="mermaid-diagram">\n'
349
+ if title:
350
+ html += f' <div class="diagram-title">{title}</div>\n'
351
+ html += f' <pre class="mermaid">\n{code}\n </pre>\n'
352
+ html += '</div>'
353
+
354
+ return html
355
+
356
+ def _render_callout(self, props: Dict[str, Any]) -> str:
357
+ """Render a callout/alert box."""
358
+ callout_type = props.get("type", "info") # info, warning, error, success
359
+ title = props.get("title", "")
360
+ content = props.get("content", "")
361
+
362
+ icons = {
363
+ "info": "ℹ️",
364
+ "warning": "⚠️",
365
+ "error": "❌",
366
+ "success": "✅",
367
+ }
368
+
369
+ html = f'<div class="callout callout-{callout_type}">\n'
370
+ html += f' <div class="callout-icon">{icons.get(callout_type, "ℹ️")}</div>\n'
371
+ html += ' <div class="callout-content">\n'
372
+ if title:
373
+ html += f' <div class="callout-title">{title}</div>\n'
374
+ html += f' <div class="callout-body">{content}</div>\n'
375
+ html += ' </div>\n'
376
+ html += '</div>'
377
+
378
+ return html
379
+
380
+ def _render_tabs(self, props: Dict[str, Any]) -> str:
381
+ """Render a tabbed interface."""
382
+ tabs = props.get("tabs", [])
383
+
384
+ html = '<div class="tabs-container">\n'
385
+ html += ' <div class="tabs-header">\n'
386
+
387
+ for i, tab in enumerate(tabs):
388
+ active = "active" if i == 0 else ""
389
+ html += f' <button class="tab-button {active}" data-tab="{i}">{tab.get("label", f"Tab {i+1}")}</button>\n'
390
+
391
+ html += ' </div>\n'
392
+ html += ' <div class="tabs-content">\n'
393
+
394
+ for i, tab in enumerate(tabs):
395
+ active = "active" if i == 0 else ""
396
+ html += f' <div class="tab-panel {active}" data-tab="{i}">{tab.get("content", "")}</div>\n'
397
+
398
+ html += ' </div>\n'
399
+ html += '</div>'
400
+
401
+ return html
402
+
403
+
404
+ class ExtendedCodeBlockParser:
405
+ """
406
+ Parse and generate extended code blocks with metadata.
407
+ """
408
+
409
+ # Language to file extension mapping
410
+ LANG_EXTENSIONS = {
411
+ "python": ".py",
412
+ "javascript": ".js",
413
+ "typescript": ".ts",
414
+ "tsx": ".tsx",
415
+ "jsx": ".jsx",
416
+ "rust": ".rs",
417
+ "go": ".go",
418
+ "java": ".java",
419
+ "cpp": ".cpp",
420
+ "c": ".c",
421
+ "html": ".html",
422
+ "css": ".css",
423
+ "json": ".json",
424
+ "yaml": ".yaml",
425
+ "markdown": ".md",
426
+ "sql": ".sql",
427
+ "bash": ".sh",
428
+ "shell": ".sh",
429
+ }
430
+
431
+ @classmethod
432
+ def parse(cls, code_block: str) -> tuple[CodeBlockMeta, str]:
433
+ """
434
+ Parse an extended code block.
435
+
436
+ Args:
437
+ code_block: Full code block including ``` markers
438
+
439
+ Returns:
440
+ Tuple of (metadata, code_content)
441
+ """
442
+ lines = code_block.strip().split('\n')
443
+ first_line = lines[0]
444
+
445
+ # Parse first line for metadata
446
+ meta = CodeBlockMeta()
447
+
448
+ # Extract language
449
+ lang_match = re.match(r'```(\w+)', first_line)
450
+ if lang_match:
451
+ meta.language = lang_match.group(1)
452
+
453
+ # Extract attributes
454
+ attrs = {}
455
+ attr_pattern = r'(\w+)="([^"]*)"'
456
+ for match in re.finditer(attr_pattern, first_line):
457
+ attrs[match.group(1)] = match.group(2)
458
+
459
+ meta.project = attrs.get("project", "")
460
+ meta.file = attrs.get("file", "")
461
+ meta.block_type = attrs.get("type", "code")
462
+ meta.title = attrs.get("title", "")
463
+ meta.executable = meta.block_type in ["react", "nodejs", "html"]
464
+
465
+ # Handle highlight lines
466
+ if "highlight" in attrs:
467
+ try:
468
+ meta.highlight_lines = [int(x) for x in attrs["highlight"].split(",")]
469
+ except ValueError:
470
+ pass
471
+
472
+ # Extract code content
473
+ code_lines = lines[1:-1] if lines[-1].strip() == '```' else lines[1:]
474
+ code = '\n'.join(code_lines)
475
+
476
+ return meta, code
477
+
478
+ @classmethod
479
+ def format(cls, code: str, meta: CodeBlockMeta) -> str:
480
+ """
481
+ Format code with extended code block syntax.
482
+
483
+ Args:
484
+ code: Code content
485
+ meta: Metadata for the code block
486
+
487
+ Returns:
488
+ Formatted code block string
489
+ """
490
+ attrs = []
491
+
492
+ if meta.project:
493
+ attrs.append(f'project="{meta.project}"')
494
+ if meta.file:
495
+ attrs.append(f'file="{meta.file}"')
496
+ if meta.block_type != "code":
497
+ attrs.append(f'type="{meta.block_type}"')
498
+ if meta.title:
499
+ attrs.append(f'title="{meta.title}"')
500
+ if meta.highlight_lines:
501
+ attrs.append(f'highlight="{",".join(str(x) for x in meta.highlight_lines)}"')
502
+
503
+ attr_str = " " + " ".join(attrs) if attrs else ""
504
+
505
+ return f"```{meta.language}{attr_str}\n{code}\n```"
506
+
507
+ @classmethod
508
+ def create_react_component(
509
+ cls,
510
+ code: str,
511
+ component_name: str,
512
+ project: str = "MiniMind",
513
+ ) -> str:
514
+ """Create a React component code block."""
515
+ meta = CodeBlockMeta(
516
+ project=project,
517
+ file=f"components/{component_name}.tsx",
518
+ block_type="react",
519
+ language="tsx",
520
+ )
521
+ return cls.format(code, meta)
522
+
523
+ @classmethod
524
+ def create_mermaid_diagram(
525
+ cls,
526
+ code: str,
527
+ title: str = "",
528
+ ) -> str:
529
+ """Create a Mermaid diagram code block."""
530
+ meta = CodeBlockMeta(
531
+ block_type="diagram",
532
+ language="mermaid",
533
+ title=title,
534
+ )
535
+ return cls.format(code, meta)
536
+
537
+
538
+ class TemplateLibrary:
539
+ """
540
+ Pre-built templates for common code patterns.
541
+ """
542
+
543
+ TEMPLATES = {
544
+ "react_component": '''
545
+ import React from 'react';
546
+
547
+ interface {{ name }}Props {
548
+ {% for prop in props %}
549
+ {{ prop.name }}: {{ prop.type }};
550
+ {% endfor %}
551
+ }
552
+
553
+ export const {{ name }}: React.FC<{{ name }}Props> = ({ {% for prop in props %}{{ prop.name }}{% if not loop.last %}, {% endif %}{% endfor %} }) => {
554
+ return (
555
+ <div className="{{ name | lower }}">
556
+ {{ content }}
557
+ </div>
558
+ );
559
+ };
560
+ ''',
561
+ "python_class": '''
562
+ class {{ name }}:
563
+ """{{ description }}"""
564
+
565
+ def __init__(self{% for param in params %}, {{ param.name }}: {{ param.type }}{% if param.default %} = {{ param.default }}{% endif %}{% endfor %}):
566
+ {% for param in params %}
567
+ self.{{ param.name }} = {{ param.name }}
568
+ {% endfor %}
569
+
570
+ {% for method in methods %}
571
+ def {{ method.name }}(self{% for arg in method.args %}, {{ arg.name }}: {{ arg.type }}{% endfor %}) -> {{ method.return_type }}:
572
+ """{{ method.description }}"""
573
+ {{ method.body }}
574
+
575
+ {% endfor %}
576
+ ''',
577
+ "api_endpoint": '''
578
+ from fastapi import APIRouter, HTTPException
579
+ from pydantic import BaseModel
580
+
581
+ router = APIRouter(prefix="/{{ prefix }}", tags=["{{ tag }}"])
582
+
583
+ {% for model in models %}
584
+ class {{ model.name }}(BaseModel):
585
+ {% for field in model.fields %}
586
+ {{ field.name }}: {{ field.type }}
587
+ {% endfor %}
588
+
589
+ {% endfor %}
590
+
591
+ {% for endpoint in endpoints %}
592
+ @router.{{ endpoint.method }}("{{ endpoint.path }}")
593
+ async def {{ endpoint.name }}({% for param in endpoint.params %}{{ param.name }}: {{ param.type }}{% if not loop.last %}, {% endif %}{% endfor %}):
594
+ """{{ endpoint.description }}"""
595
+ {{ endpoint.body }}
596
+
597
+ {% endfor %}
598
+ ''',
599
+ }
600
+
601
+ def __init__(self):
602
+ self.engine = JinjaTemplateEngine()
603
+
604
+ def render(self, template_name: str, context: Dict[str, Any]) -> str:
605
+ """Render a pre-built template."""
606
+ if template_name not in self.TEMPLATES:
607
+ raise ValueError(f"Unknown template: {template_name}")
608
+
609
+ template = self.TEMPLATES[template_name]
610
+ return self.engine.render(template, context)
611
+
612
+ def add_template(self, name: str, template: str):
613
+ """Add a custom template."""
614
+ self.TEMPLATES[name] = template