notionary 0.1.19__py3-none-any.whl → 0.1.21__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 (42) hide show
  1. notionary/__init__.py +2 -2
  2. notionary/database/notion_database.py +3 -1
  3. notionary/elements/audio_element.py +7 -11
  4. notionary/elements/bookmark_element.py +17 -29
  5. notionary/elements/bulleted_list_element.py +69 -0
  6. notionary/elements/callout_element.py +18 -80
  7. notionary/elements/code_block_element.py +15 -10
  8. notionary/elements/column_element.py +39 -26
  9. notionary/elements/divider_element.py +8 -25
  10. notionary/elements/embed_element.py +10 -18
  11. notionary/elements/heading_element.py +10 -12
  12. notionary/elements/image_element.py +9 -15
  13. notionary/elements/mention_element.py +6 -15
  14. notionary/elements/notion_block_element.py +12 -11
  15. notionary/elements/numbered_list_element.py +68 -0
  16. notionary/elements/paragraph_element.py +11 -7
  17. notionary/elements/prompts/element_prompt_content.py +20 -0
  18. notionary/elements/prompts/synthax_prompt_builder.py +92 -0
  19. notionary/elements/qoute_element.py +20 -89
  20. notionary/elements/registry/block_element_registry.py +90 -0
  21. notionary/elements/{block_element_registry_builder.py → registry/block_element_registry_builder.py} +15 -124
  22. notionary/elements/table_element.py +4 -16
  23. notionary/elements/text_inline_formatter.py +15 -78
  24. notionary/elements/todo_lists.py +14 -18
  25. notionary/elements/toggle_element.py +19 -1
  26. notionary/elements/video_element.py +10 -19
  27. notionary/notion_client.py +2 -2
  28. notionary/page/content/page_content_manager.py +2 -3
  29. notionary/page/markdown_to_notion_converter.py +3 -3
  30. notionary/page/notion_page.py +3 -3
  31. notionary/page/notion_to_markdown_converter.py +3 -3
  32. notionary/page/relations/notion_page_relation_manager.py +24 -24
  33. notionary/page/relations/notion_page_title_resolver.py +1 -2
  34. {notionary-0.1.19.dist-info → notionary-0.1.21.dist-info}/METADATA +3 -3
  35. notionary-0.1.21.dist-info/RECORD +58 -0
  36. {notionary-0.1.19.dist-info → notionary-0.1.21.dist-info}/WHEEL +1 -1
  37. notionary/elements/block_element_registry.py +0 -233
  38. notionary/elements/list_element.py +0 -130
  39. notionary/util/singleton_decorator.py +0 -20
  40. notionary-0.1.19.dist-info/RECORD +0 -56
  41. {notionary-0.1.19.dist-info → notionary-0.1.21.dist-info}/licenses/LICENSE +0 -0
  42. {notionary-0.1.19.dist-info → notionary-0.1.21.dist-info}/top_level.txt +0 -0
@@ -1,10 +1,8 @@
1
- # File: elements/dividers.py
2
-
3
- from typing import Dict, Any, Optional
4
- from typing_extensions import override
5
1
  import re
2
+ from typing import Dict, Any, Optional
6
3
 
7
4
  from notionary.elements.notion_block_element import NotionBlockElement
5
+ from notionary.elements.prompts.element_prompt_content import ElementPromptContent
8
6
 
9
7
 
10
8
  class DividerElement(NotionBlockElement):
@@ -17,19 +15,16 @@ class DividerElement(NotionBlockElement):
17
15
 
18
16
  PATTERN = re.compile(r"^\s*-{3,}\s*$")
19
17
 
20
- @override
21
18
  @staticmethod
22
19
  def match_markdown(text: str) -> bool:
23
20
  """Check if text is a markdown divider."""
24
21
  return bool(DividerElement.PATTERN.match(text))
25
22
 
26
- @override
27
23
  @staticmethod
28
24
  def match_notion(block: Dict[str, Any]) -> bool:
29
25
  """Check if block is a Notion divider."""
30
26
  return block.get("type") == "divider"
31
27
 
32
- @override
33
28
  @staticmethod
34
29
  def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
35
30
  """Convert markdown divider to Notion divider block."""
@@ -38,7 +33,6 @@ class DividerElement(NotionBlockElement):
38
33
 
39
34
  return {"type": "divider", "divider": {}}
40
35
 
41
- @override
42
36
  @staticmethod
43
37
  def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
44
38
  """Convert Notion divider block to markdown divider."""
@@ -47,27 +41,16 @@ class DividerElement(NotionBlockElement):
47
41
 
48
42
  return "---"
49
43
 
50
- @override
51
44
  @staticmethod
52
45
  def is_multiline() -> bool:
53
46
  return False
54
47
 
55
48
  @classmethod
56
- def get_llm_prompt_content(cls) -> dict:
57
- """
58
- Returns a dictionary with all information needed for LLM prompts about this element.
59
- Includes description, usage guidance, syntax options, and examples.
60
- """
49
+ def get_llm_prompt_content(cls) -> ElementPromptContent:
50
+ """Returns compact LLM prompt metadata for the divider element."""
61
51
  return {
62
- "description": "Creates a horizontal divider line that visually separates sections of content.",
63
- "when_to_use": "Use dividers when you want to create clear visual breaks between different sections or topics in your document. Dividers help improve readability by organizing content into distinct sections without requiring headings.",
64
- "syntax": ["---"],
65
- "notes": [
66
- "Dividers must be on their own line with no other content",
67
- "Dividers work well when combined with headings to clearly separate major document sections",
68
- ],
69
- "examples": [
70
- "## Introduction\nThis is the introduction section of the document.\n\n---\n\n## Main Content\nThis is the main content section.",
71
- "Task List:\n- Complete project proposal\n- Review feedback\n\n---\n\nMeeting Notes:\n- Discussed timeline\n- Assigned responsibilities",
72
- ],
52
+ "description": "Creates a horizontal divider line to visually separate sections of content.",
53
+ "when_to_use": "Use to create clear visual breaks between different sections without requiring headings.",
54
+ "syntax": "---",
55
+ "examples": ["## Section 1\nContent\n\n---\n\n## Section 2\nMore content"],
73
56
  }
@@ -1,6 +1,7 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional, List
3
3
  from notionary.elements.notion_block_element import NotionBlockElement
4
+ from notionary.elements.prompts.element_prompt_content import ElementPromptContent
4
5
 
5
6
 
6
7
  class EmbedElement(NotionBlockElement):
@@ -97,26 +98,17 @@ class EmbedElement(NotionBlockElement):
97
98
  return result
98
99
 
99
100
  @classmethod
100
- def get_llm_prompt_content(cls) -> dict:
101
- """Returns information for LLM prompts about this element."""
101
+ def get_llm_prompt_content(cls) -> ElementPromptContent:
102
+ """
103
+ Returns structured LLM prompt metadata for the embed element.
104
+ """
102
105
  return {
103
106
  "description": "Embeds external content from websites, PDFs, Google Maps, and other sources directly in your document.",
104
- "when_to_use": "Use embeds when you want to include external content that isn't just a video or image. Embeds are great for interactive content, reference materials, or live data sources.",
105
- "syntax": [
106
- "<embed>(https://example.com) - Embed without caption",
107
- "<embed:Caption text>(https://example.com) - Embed with caption",
108
- ],
109
- "supported_sources": [
110
- "Websites and web pages",
111
- "PDFs and documents",
112
- "Google Maps",
113
- "Google Drive files",
114
- "Twitter/X posts",
115
- "GitHub repositories and code",
116
- "Figma designs",
117
- "Miro boards",
118
- "Many other services supported by Notion's embed feature",
119
- ],
107
+ "when_to_use": (
108
+ "Use embeds when you want to include external content that isn't just a video or image. "
109
+ "Embeds are great for interactive content, reference materials, or live data sources."
110
+ ),
111
+ "syntax": "<embed:Caption>(https://example.com)",
120
112
  "examples": [
121
113
  "<embed:Course materials>(https://drive.google.com/file/d/123456/view)",
122
114
  "<embed:Our office location>(https://www.google.com/maps?q=San+Francisco)",
@@ -1,8 +1,8 @@
1
- from typing import Dict, Any, Optional
2
- from typing_extensions import override
3
1
  import re
2
+ from typing import Dict, Any, Optional
4
3
 
5
4
  from notionary.elements.notion_block_element import NotionBlockElement
5
+ from notionary.elements.prompts.element_prompt_content import ElementPromptContent
6
6
  from notionary.elements.text_inline_formatter import TextInlineFormatter
7
7
 
8
8
 
@@ -11,20 +11,17 @@ class HeadingElement(NotionBlockElement):
11
11
 
12
12
  PATTERN = re.compile(r"^(#{1,6})\s(.+)$")
13
13
 
14
- @override
15
14
  @staticmethod
16
15
  def match_markdown(text: str) -> bool:
17
16
  """Check if text is a markdown heading."""
18
17
  return bool(HeadingElement.PATTERN.match(text))
19
18
 
20
- @override
21
19
  @staticmethod
22
20
  def match_notion(block: Dict[str, Any]) -> bool:
23
21
  """Check if block is a Notion heading."""
24
22
  block_type: str = block.get("type", "")
25
23
  return block_type.startswith("heading_") and block_type[-1] in "123456"
26
24
 
27
- @override
28
25
  @staticmethod
29
26
  def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
30
27
  """Convert markdown heading to Notion heading block."""
@@ -35,8 +32,6 @@ class HeadingElement(NotionBlockElement):
35
32
  level = len(header_match.group(1))
36
33
  content = header_match.group(2)
37
34
 
38
- # Import here to avoid circular imports
39
-
40
35
  return {
41
36
  "type": f"heading_{level}",
42
37
  f"heading_{level}": {
@@ -44,7 +39,6 @@ class HeadingElement(NotionBlockElement):
44
39
  },
45
40
  }
46
41
 
47
- @override
48
42
  @staticmethod
49
43
  def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
50
44
  """Convert Notion heading block to markdown heading."""
@@ -67,18 +61,22 @@ class HeadingElement(NotionBlockElement):
67
61
  prefix = "#" * level
68
62
  return f"{prefix} {text or ''}"
69
63
 
70
- @override
71
64
  @staticmethod
72
65
  def is_multiline() -> bool:
73
66
  return False
74
67
 
75
- @override
76
68
  @classmethod
77
- def get_llm_prompt_content(cls) -> dict:
69
+ def get_llm_prompt_content(cls) -> ElementPromptContent:
78
70
  """
79
- Returns a dictionary with all information needed for LLM prompts about this element.
71
+ Returns structured LLM prompt metadata for the heading element.
80
72
  """
81
73
  return {
82
74
  "description": "Use Markdown headings (#, ##, ###, etc.) to structure content hierarchically.",
83
75
  "when_to_use": "Use to group content into sections and define a visual hierarchy.",
76
+ "syntax": "## Your Heading Text",
77
+ "examples": [
78
+ "# Main Title",
79
+ "## Section Title",
80
+ "### Subsection Title",
81
+ ],
84
82
  }
@@ -1,7 +1,7 @@
1
1
  import re
2
2
  from typing import Dict, Any, Optional, List
3
- from typing_extensions import override
4
3
  from notionary.elements.notion_block_element import NotionBlockElement
4
+ from notionary.elements.prompts.element_prompt_content import ElementPromptContent
5
5
 
6
6
 
7
7
  class ImageElement(NotionBlockElement):
@@ -22,7 +22,6 @@ class ImageElement(NotionBlockElement):
22
22
  + r"\)$" # closing parenthesis
23
23
  )
24
24
 
25
- @override
26
25
  @staticmethod
27
26
  def match_markdown(text: str) -> bool:
28
27
  """Check if text is a markdown image."""
@@ -30,13 +29,11 @@ class ImageElement(NotionBlockElement):
30
29
  ImageElement.PATTERN.match(text.strip())
31
30
  )
32
31
 
33
- @override
34
32
  @staticmethod
35
33
  def match_notion(block: Dict[str, Any]) -> bool:
36
34
  """Check if block is a Notion image."""
37
35
  return block.get("type") == "image"
38
36
 
39
- @override
40
37
  @staticmethod
41
38
  def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
42
39
  """Convert markdown image to Notion image block."""
@@ -64,7 +61,6 @@ class ImageElement(NotionBlockElement):
64
61
 
65
62
  return image_block
66
63
 
67
- @override
68
64
  @staticmethod
69
65
  def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
70
66
  """Convert Notion image block to markdown image."""
@@ -103,25 +99,23 @@ class ImageElement(NotionBlockElement):
103
99
  result += text_obj.get("plain_text", "")
104
100
  return result
105
101
 
106
- @override
107
102
  @staticmethod
108
103
  def is_multiline() -> bool:
109
104
  return False
110
105
 
111
106
  @classmethod
112
- def get_llm_prompt_content(cls) -> dict:
107
+ def get_llm_prompt_content(cls) -> ElementPromptContent:
113
108
  """
114
- Returns a dictionary with all information needed for LLM prompts about this element.
115
- Includes description, usage guidance, syntax options, and examples.
109
+ Returns structured LLM prompt metadata for the image element.
116
110
  """
117
111
  return {
118
112
  "description": "Embeds an image from an external URL into your document.",
119
- "when_to_use": "Use images to include visual content such as diagrams, screenshots, charts, photos, or illustrations that enhance your document. Images can make complex information easier to understand, create visual interest, or provide evidence for your points.",
120
- "syntax": [
121
- "![](https://example.com/image.jpg) - Image without caption",
122
- "![Caption text](https://example.com/image.jpg) - Image with caption",
123
- '![Caption text](https://example.com/image.jpg "Alt text") - Image with caption and alt text',
124
- ],
113
+ "when_to_use": (
114
+ "Use images to include visual content such as diagrams, screenshots, charts, photos, or illustrations "
115
+ "that enhance your document. Images can make complex information easier to understand, create visual interest, "
116
+ "or provide evidence for your points."
117
+ ),
118
+ "syntax": "![Caption](https://example.com/image.jpg)",
125
119
  "examples": [
126
120
  "![Data visualization showing monthly trends](https://example.com/chart.png)",
127
121
  "![](https://example.com/screenshot.jpg)",
@@ -3,6 +3,7 @@ from typing import Dict, Any, Optional, List
3
3
  from typing_extensions import override
4
4
 
5
5
  from notionary.elements.notion_block_element import NotionBlockElement
6
+ from notionary.elements.prompts.element_prompt_content import ElementPromptContent
6
7
 
7
8
 
8
9
  class MentionElement(NotionBlockElement):
@@ -45,7 +46,6 @@ class MentionElement(NotionBlockElement):
45
46
  },
46
47
  }
47
48
 
48
- @override
49
49
  @staticmethod
50
50
  def match_markdown(text: str) -> bool:
51
51
  """Check if text contains a markdown mention."""
@@ -54,7 +54,6 @@ class MentionElement(NotionBlockElement):
54
54
  return True
55
55
  return False
56
56
 
57
- @override
58
57
  @staticmethod
59
58
  def match_notion(block: Dict[str, Any]) -> bool:
60
59
  """Check if block contains a mention."""
@@ -75,7 +74,6 @@ class MentionElement(NotionBlockElement):
75
74
 
76
75
  return any(text_item.get("type") == "mention" for text_item in rich_text)
77
76
 
78
- @override
79
77
  @staticmethod
80
78
  def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
81
79
  """Convert markdown text with mentions to a Notion paragraph block."""
@@ -161,7 +159,6 @@ class MentionElement(NotionBlockElement):
161
159
  "color": "default",
162
160
  }
163
161
 
164
- @override
165
162
  @staticmethod
166
163
  def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
167
164
  """Extract mentions from Notion block and convert to markdown format."""
@@ -200,28 +197,22 @@ class MentionElement(NotionBlockElement):
200
197
 
201
198
  return "".join(result)
202
199
 
203
- @override
204
200
  @staticmethod
205
201
  def is_multiline() -> bool:
206
202
  return False
207
203
 
208
204
  @classmethod
209
- def get_llm_prompt_content(cls) -> dict:
210
- """Information about this element for LLM-based processing."""
205
+ def get_llm_prompt_content(cls) -> ElementPromptContent:
206
+ """
207
+ Returns structured LLM prompt metadata for the mention element.
208
+ """
211
209
  return {
212
210
  "description": "References to Notion pages, databases, or dates within text content.",
213
211
  "when_to_use": "When you want to link to other Notion content within your text.",
214
- "syntax": [
215
- "@[page-id] - Reference to a Notion page",
216
- "@date[YYYY-MM-DD] - Reference to a date",
217
- "@db[database-id] - Reference to a Notion database",
218
- ],
212
+ "syntax": "@[page-id]",
219
213
  "examples": [
220
214
  "Check the meeting notes at @[1a6389d5-7bd3-80c5-9a87-e90b034989d0]",
221
215
  "Deadline is @date[2023-12-31]",
222
216
  "Use the structure in @db[1a6389d5-7bd3-80e9-b199-000cfb3fa0b3]",
223
217
  ],
224
- "limitations": [
225
- "Mentions require knowing the internal IDs of the pages or databases you want to reference"
226
- ],
227
218
  }
@@ -2,6 +2,8 @@ import inspect
2
2
  from typing import Dict, Any, Optional
3
3
  from abc import ABC
4
4
 
5
+ from notionary.elements.prompts.element_prompt_content import ElementPromptContent
6
+
5
7
 
6
8
  class NotionBlockElement(ABC):
7
9
  """Base class for elements that can be converted between Markdown and Notion."""
@@ -37,15 +39,14 @@ class NotionBlockElement(ABC):
37
39
  By default, returns the class docstring.
38
40
  """
39
41
 
42
+ @classmethod
43
+ def get_llm_prompt_content(cls) -> ElementPromptContent:
44
+ """
45
+ Returns a dictionary with information for LLM prompts about this element.
46
+ This default implementation extracts information from the class docstring.
47
+ Subclasses should override this method to provide more structured information.
40
48
 
41
- @classmethod
42
- def get_llm_prompt_content(cls) -> dict:
43
- """
44
- Returns a dictionary with information for LLM prompts about this element.
45
- This default implementation extracts information from the class docstring.
46
- Subclasses should override this method to provide more structured information.
47
-
48
- Returns:
49
- Dictionary with documentation information
50
- """
51
- return {"description": inspect.cleandoc(cls.__doc__ or ""), "examples": []}
49
+ Returns:
50
+ Dictionary with documentation information
51
+ """
52
+ return {"description": inspect.cleandoc(cls.__doc__ or ""), "examples": []}
@@ -0,0 +1,68 @@
1
+ import re
2
+ from typing import Dict, Any, Optional
3
+ from notionary.elements.notion_block_element import NotionBlockElement
4
+ from notionary.elements.prompts.element_prompt_content import ElementPromptContent
5
+ from notionary.elements.text_inline_formatter import TextInlineFormatter
6
+
7
+
8
+ class NumberedListElement(NotionBlockElement):
9
+ """Class for converting between Markdown numbered lists and Notion numbered list items."""
10
+
11
+ @staticmethod
12
+ def markdown_to_notion(text: str) -> Optional[Dict[str, Any]]:
13
+ """Convert markdown numbered list item to Notion block."""
14
+ pattern = re.compile(r"^\s*(\d+)\.\s+(.+)$")
15
+ numbered_match = pattern.match(text)
16
+ if not numbered_match:
17
+ return None
18
+
19
+ content = numbered_match.group(2)
20
+
21
+ # Use parse_inline_formatting to handle rich text
22
+ rich_text = TextInlineFormatter.parse_inline_formatting(content)
23
+
24
+ return {
25
+ "type": "numbered_list_item",
26
+ "numbered_list_item": {"rich_text": rich_text, "color": "default"},
27
+ }
28
+
29
+ @staticmethod
30
+ def notion_to_markdown(block: Dict[str, Any]) -> Optional[str]:
31
+ """Convert Notion numbered list item block to markdown."""
32
+ if block.get("type") != "numbered_list_item":
33
+ return None
34
+
35
+ rich_text = block.get("numbered_list_item", {}).get("rich_text", [])
36
+ content = TextInlineFormatter.extract_text_with_formatting(rich_text)
37
+
38
+ return f"1. {content}"
39
+
40
+ @staticmethod
41
+ def match_markdown(text: str) -> bool:
42
+ """Check if this element can handle the given markdown text."""
43
+ pattern = re.compile(r"^\s*\d+\.\s+(.+)$")
44
+ return bool(pattern.match(text))
45
+
46
+ @staticmethod
47
+ def match_notion(block: Dict[str, Any]) -> bool:
48
+ """Check if this element can handle the given Notion block."""
49
+ return block.get("type") == "numbered_list_item"
50
+
51
+ @staticmethod
52
+ def is_multiline() -> bool:
53
+ return False
54
+
55
+ @classmethod
56
+ def get_llm_prompt_content(cls) -> ElementPromptContent:
57
+ """
58
+ Returns structured LLM prompt metadata for the numbered list element.
59
+ """
60
+ return {
61
+ "description": "Creates numbered list items for ordered sequences.",
62
+ "when_to_use": "Use for lists where order matters, such as steps, rankings, or sequential items.",
63
+ "syntax": "1. Item text",
64
+ "examples": [
65
+ "1. First step\n2. Second step\n3. Third step",
66
+ "1. Gather materials\n2. Assemble parts\n3. Test the result",
67
+ ],
68
+ }
@@ -2,6 +2,7 @@ from typing import Dict, Any, Optional
2
2
  from typing_extensions import override
3
3
 
4
4
  from notionary.elements.notion_block_element import NotionBlockElement
5
+ from notionary.elements.prompts.element_prompt_content import ElementPromptContent
5
6
  from notionary.elements.text_inline_formatter import TextInlineFormatter
6
7
 
7
8
 
@@ -57,17 +58,20 @@ class ParagraphElement(NotionBlockElement):
57
58
  return False
58
59
 
59
60
  @classmethod
60
- def get_llm_prompt_content(cls) -> dict:
61
- """Returns information for LLM prompts about this element."""
61
+ def get_llm_prompt_content(cls) -> ElementPromptContent:
62
+ """
63
+ Returns structured LLM prompt metadata for the paragraph element.
64
+ """
62
65
  return {
63
66
  "description": "Creates standard paragraph blocks for regular text content.",
64
- "when_to_use": "Use paragraphs for normal text content. Paragraphs are the default block type and will be used when no other specific formatting is applied.",
65
- "syntax": ["Just write text normally without any special prefix"],
66
- "notes": [
67
- "Paragraphs support inline formatting like **bold**, *italic*, ~~strikethrough~~, `code`, and [links](url)"
68
- ],
67
+ "when_to_use": (
68
+ "Use paragraphs for normal text content. Paragraphs are the default block type and will be used "
69
+ "when no other specific formatting is applied."
70
+ ),
71
+ "syntax": "Just write text normally without any special prefix",
69
72
  "examples": [
70
73
  "This is a simple paragraph with plain text.",
71
74
  "This paragraph has **bold** and *italic* formatting.",
75
+ "You can also include [links](https://example.com) or `inline code`.",
72
76
  ],
73
77
  }
@@ -0,0 +1,20 @@
1
+ from typing import TypedDict, List
2
+
3
+
4
+ class ElementPromptContent(TypedDict):
5
+ """
6
+ Typed dictionary defining the standardized structure for element prompt content.
7
+ This ensures consistent formatting across all Notion block elements.
8
+ """
9
+
10
+ description: str
11
+ """Concise explanation of what the element is and its purpose in Notion."""
12
+
13
+ syntax: str
14
+ """The exact markdown syntax pattern used to create this element."""
15
+
16
+ examples: List[str]
17
+ """List of practical usage examples showing the element in context."""
18
+
19
+ when_to_use: str
20
+ """Guidelines explaining the appropriate scenarios for using this element."""
@@ -0,0 +1,92 @@
1
+ from typing import Type, List
2
+ from notionary.elements.notion_block_element import NotionBlockElement
3
+
4
+
5
+ class MarkdownSyntaxPromptBuilder:
6
+ """
7
+ Generator for LLM system prompts that describe Notion-Markdown syntax.
8
+
9
+ This class extracts information about supported Markdown patterns
10
+ and formats them optimally for LLMs.
11
+ """
12
+
13
+ SYSTEM_PROMPT_TEMPLATE = """You are a knowledgeable assistant that helps users create content for Notion pages.
14
+ Notion supports standard Markdown with some special extensions for creating rich content.
15
+
16
+ {element_docs}
17
+
18
+ Important usage guidelines:
19
+
20
+ 1. Do NOT start content with a level 1 heading (# Heading). In Notion, the page title is already displayed in the metadata, so starting with an H1 heading is redundant. Begin with H2 (## Heading) or lower for section headings.
21
+
22
+ 2. The backtick code fence syntax (```) should ONLY be used when creating actual code blocks or diagrams.
23
+ Do not wrap examples or regular content in backticks unless you're showing code.
24
+
25
+ 3. Use inline formatting (bold, italic, etc.) across all content to enhance readability.
26
+ Proper typography is essential for creating scannable, well-structured documents.
27
+
28
+ 4. Notion's extensions to Markdown provide richer formatting options than standard Markdown
29
+ while maintaining the familiar Markdown syntax for basic elements.
30
+
31
+ 5. Always structure content with clear headings, lists, and paragraphs to create visually appealing
32
+ and well-organized documents.
33
+ """
34
+
35
+ @staticmethod
36
+ def generate_element_doc(element_class: Type[NotionBlockElement]) -> str:
37
+ """
38
+ Generates documentation for a specific NotionBlockElement in a compact format.
39
+ Uses the element's get_llm_prompt_content method if available.
40
+ """
41
+ class_name = element_class.__name__
42
+ element_name = class_name.replace("Element", "")
43
+
44
+ # Check if the class has the get_llm_prompt_content method
45
+ if not hasattr(element_class, "get_llm_prompt_content") or not callable(
46
+ getattr(element_class, "get_llm_prompt_content")
47
+ ):
48
+ return f"## {element_name}"
49
+
50
+ # Get the element content
51
+ content = element_class.get_llm_prompt_content()
52
+
53
+ # Format the element documentation in a compact way
54
+ doc_parts = [
55
+ f"## {element_name}",
56
+ f"{content['description']}",
57
+ f"**Syntax:** {content['syntax']}",
58
+ f"**Example:** {content['examples'][0]}" if content["examples"] else "",
59
+ f"**When to use:** {content['when_to_use']}",
60
+ ]
61
+
62
+ # Filter out any empty parts and join with newlines
63
+ return "\n".join([part for part in doc_parts if part])
64
+
65
+ @classmethod
66
+ def generate_element_docs(
67
+ cls, element_classes: List[Type[NotionBlockElement]]
68
+ ) -> str:
69
+ """
70
+ Generates complete documentation for all provided element classes.
71
+ """
72
+ docs = [
73
+ "# Markdown Syntax for Notion Blocks",
74
+ "The following Markdown patterns are supported for creating Notion blocks:",
75
+ ]
76
+
77
+ # Generate docs for each element
78
+ for element in element_classes:
79
+ docs.append("\n" + cls.generate_element_doc(element))
80
+
81
+ return "\n".join(docs)
82
+
83
+ @classmethod
84
+ def generate_system_prompt(
85
+ cls,
86
+ element_classes: List[Type[NotionBlockElement]],
87
+ ) -> str:
88
+ """
89
+ Generates a complete system prompt for LLMs.
90
+ """
91
+ element_docs = cls.generate_element_docs(element_classes)
92
+ return cls.SYSTEM_PROMPT_TEMPLATE.format(element_docs=element_docs)