notionary 0.4.0__py3-none-any.whl → 0.4.1__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 (176) hide show
  1. notionary/__init__.py +44 -1
  2. notionary/blocks/client.py +37 -11
  3. notionary/blocks/rich_text/markdown_rich_text_converter.py +49 -15
  4. notionary/blocks/rich_text/models.py +13 -4
  5. notionary/blocks/rich_text/name_id_resolver/data_source.py +9 -3
  6. notionary/blocks/rich_text/name_id_resolver/person.py +6 -2
  7. notionary/blocks/rich_text/rich_text_markdown_converter.py +10 -3
  8. notionary/blocks/schemas.py +2 -1
  9. notionary/comments/client.py +19 -6
  10. notionary/comments/factory.py +10 -3
  11. notionary/comments/schemas.py +9 -3
  12. notionary/comments/service.py +12 -4
  13. notionary/data_source/http/data_source_instance_client.py +59 -17
  14. notionary/data_source/properties/schemas.py +30 -10
  15. notionary/data_source/query/builder.py +67 -18
  16. notionary/data_source/query/resolver.py +16 -5
  17. notionary/data_source/query/schema.py +24 -6
  18. notionary/data_source/query/validator.py +18 -6
  19. notionary/data_source/schema/registry.py +31 -12
  20. notionary/data_source/schema/service.py +66 -20
  21. notionary/data_source/service.py +74 -23
  22. notionary/database/client.py +27 -9
  23. notionary/database/database_metadata_update_client.py +12 -4
  24. notionary/database/service.py +11 -4
  25. notionary/exceptions/__init__.py +15 -3
  26. notionary/exceptions/block_parsing.py +6 -2
  27. notionary/exceptions/data_source/builder.py +11 -5
  28. notionary/exceptions/data_source/properties.py +3 -1
  29. notionary/exceptions/file_upload.py +12 -3
  30. notionary/exceptions/properties.py +3 -1
  31. notionary/exceptions/search.py +6 -2
  32. notionary/file_upload/client.py +5 -1
  33. notionary/file_upload/config/config.py +10 -3
  34. notionary/file_upload/query/builder.py +6 -2
  35. notionary/file_upload/schemas.py +3 -1
  36. notionary/file_upload/service.py +42 -14
  37. notionary/file_upload/validation/factory.py +3 -1
  38. notionary/file_upload/validation/impl/file_name_length.py +3 -1
  39. notionary/file_upload/validation/models.py +15 -5
  40. notionary/file_upload/validation/validators/file_extension.py +12 -3
  41. notionary/http/client.py +27 -8
  42. notionary/page/content/__init__.py +9 -0
  43. notionary/page/content/factory.py +21 -7
  44. notionary/page/content/markdown/builder.py +85 -23
  45. notionary/page/content/markdown/nodes/audio.py +8 -4
  46. notionary/page/content/markdown/nodes/base.py +3 -3
  47. notionary/page/content/markdown/nodes/bookmark.py +5 -3
  48. notionary/page/content/markdown/nodes/breadcrumb.py +2 -2
  49. notionary/page/content/markdown/nodes/bulleted_list.py +5 -3
  50. notionary/page/content/markdown/nodes/callout.py +2 -2
  51. notionary/page/content/markdown/nodes/code.py +5 -3
  52. notionary/page/content/markdown/nodes/columns.py +3 -3
  53. notionary/page/content/markdown/nodes/container.py +9 -5
  54. notionary/page/content/markdown/nodes/divider.py +2 -2
  55. notionary/page/content/markdown/nodes/embed.py +8 -4
  56. notionary/page/content/markdown/nodes/equation.py +4 -2
  57. notionary/page/content/markdown/nodes/file.py +8 -4
  58. notionary/page/content/markdown/nodes/heading.py +2 -2
  59. notionary/page/content/markdown/nodes/image.py +8 -4
  60. notionary/page/content/markdown/nodes/mixins/caption.py +5 -3
  61. notionary/page/content/markdown/nodes/numbered_list.py +5 -3
  62. notionary/page/content/markdown/nodes/paragraph.py +4 -2
  63. notionary/page/content/markdown/nodes/pdf.py +8 -4
  64. notionary/page/content/markdown/nodes/quote.py +2 -2
  65. notionary/page/content/markdown/nodes/space.py +2 -2
  66. notionary/page/content/markdown/nodes/table.py +8 -5
  67. notionary/page/content/markdown/nodes/table_of_contents.py +2 -2
  68. notionary/page/content/markdown/nodes/todo.py +15 -7
  69. notionary/page/content/markdown/nodes/toggle.py +2 -2
  70. notionary/page/content/markdown/nodes/video.py +8 -4
  71. notionary/page/content/markdown/structured_output/__init__.py +73 -0
  72. notionary/page/content/markdown/structured_output/models.py +391 -0
  73. notionary/page/content/markdown/structured_output/service.py +211 -0
  74. notionary/page/content/parser/context.py +1 -1
  75. notionary/page/content/parser/factory.py +23 -8
  76. notionary/page/content/parser/parsers/audio.py +7 -2
  77. notionary/page/content/parser/parsers/base.py +2 -2
  78. notionary/page/content/parser/parsers/bookmark.py +2 -2
  79. notionary/page/content/parser/parsers/breadcrumb.py +2 -2
  80. notionary/page/content/parser/parsers/bulleted_list.py +19 -6
  81. notionary/page/content/parser/parsers/callout.py +15 -5
  82. notionary/page/content/parser/parsers/caption.py +9 -3
  83. notionary/page/content/parser/parsers/code.py +21 -7
  84. notionary/page/content/parser/parsers/column.py +8 -4
  85. notionary/page/content/parser/parsers/column_list.py +19 -7
  86. notionary/page/content/parser/parsers/divider.py +2 -2
  87. notionary/page/content/parser/parsers/embed.py +2 -2
  88. notionary/page/content/parser/parsers/equation.py +8 -4
  89. notionary/page/content/parser/parsers/file.py +7 -2
  90. notionary/page/content/parser/parsers/file_like_block.py +30 -10
  91. notionary/page/content/parser/parsers/heading.py +31 -10
  92. notionary/page/content/parser/parsers/image.py +7 -2
  93. notionary/page/content/parser/parsers/numbered_list.py +18 -6
  94. notionary/page/content/parser/parsers/paragraph.py +3 -1
  95. notionary/page/content/parser/parsers/pdf.py +7 -2
  96. notionary/page/content/parser/parsers/quote.py +28 -9
  97. notionary/page/content/parser/parsers/space.py +2 -2
  98. notionary/page/content/parser/parsers/table.py +31 -10
  99. notionary/page/content/parser/parsers/table_of_contents.py +7 -3
  100. notionary/page/content/parser/parsers/todo.py +15 -5
  101. notionary/page/content/parser/parsers/toggle.py +15 -5
  102. notionary/page/content/parser/parsers/video.py +7 -2
  103. notionary/page/content/parser/post_processing/handlers/rich_text_length.py +8 -2
  104. notionary/page/content/parser/post_processing/handlers/rich_text_length_truncation.py +8 -2
  105. notionary/page/content/parser/post_processing/service.py +3 -1
  106. notionary/page/content/parser/pre_processsing/handlers/column_syntax.py +21 -7
  107. notionary/page/content/parser/pre_processsing/handlers/indentation.py +11 -4
  108. notionary/page/content/parser/pre_processsing/handlers/video_syntax.py +13 -6
  109. notionary/page/content/parser/service.py +4 -1
  110. notionary/page/content/renderer/context.py +15 -5
  111. notionary/page/content/renderer/factory.py +12 -6
  112. notionary/page/content/renderer/post_processing/handlers/numbered_list.py +19 -9
  113. notionary/page/content/renderer/renderers/audio.py +14 -5
  114. notionary/page/content/renderer/renderers/base.py +3 -3
  115. notionary/page/content/renderer/renderers/bookmark.py +3 -1
  116. notionary/page/content/renderer/renderers/bulleted_list.py +11 -5
  117. notionary/page/content/renderer/renderers/callout.py +19 -7
  118. notionary/page/content/renderer/renderers/captioned_block.py +11 -5
  119. notionary/page/content/renderer/renderers/code.py +6 -2
  120. notionary/page/content/renderer/renderers/column.py +3 -1
  121. notionary/page/content/renderer/renderers/column_list.py +3 -1
  122. notionary/page/content/renderer/renderers/embed.py +3 -1
  123. notionary/page/content/renderer/renderers/equation.py +3 -1
  124. notionary/page/content/renderer/renderers/file.py +14 -5
  125. notionary/page/content/renderer/renderers/file_like_block.py +8 -4
  126. notionary/page/content/renderer/renderers/heading.py +22 -8
  127. notionary/page/content/renderer/renderers/image.py +13 -4
  128. notionary/page/content/renderer/renderers/numbered_list.py +8 -3
  129. notionary/page/content/renderer/renderers/paragraph.py +12 -4
  130. notionary/page/content/renderer/renderers/pdf.py +14 -5
  131. notionary/page/content/renderer/renderers/quote.py +14 -6
  132. notionary/page/content/renderer/renderers/table.py +15 -5
  133. notionary/page/content/renderer/renderers/todo.py +16 -6
  134. notionary/page/content/renderer/renderers/toggle.py +8 -4
  135. notionary/page/content/renderer/renderers/video.py +14 -5
  136. notionary/page/content/renderer/service.py +9 -3
  137. notionary/page/content/service.py +21 -7
  138. notionary/page/content/syntax/definition/__init__.py +11 -0
  139. notionary/page/content/syntax/definition/models.py +57 -0
  140. notionary/page/content/syntax/definition/registry.py +371 -0
  141. notionary/page/content/syntax/prompts/__init__.py +4 -0
  142. notionary/page/content/syntax/prompts/models.py +11 -0
  143. notionary/page/content/syntax/prompts/registry.py +703 -0
  144. notionary/page/page_metadata_update_client.py +12 -4
  145. notionary/page/properties/client.py +45 -15
  146. notionary/page/properties/factory.py +6 -2
  147. notionary/page/properties/service.py +110 -36
  148. notionary/page/service.py +20 -6
  149. notionary/shared/entity/client.py +6 -2
  150. notionary/shared/entity/dto_parsers.py +3 -1
  151. notionary/shared/entity/entity_metadata_update_client.py +9 -3
  152. notionary/shared/entity/service.py +53 -22
  153. notionary/shared/models/file.py +3 -1
  154. notionary/user/base.py +6 -2
  155. notionary/user/bot.py +10 -2
  156. notionary/user/client.py +3 -1
  157. notionary/user/person.py +3 -1
  158. notionary/user/schemas.py +3 -1
  159. notionary/user/service.py +6 -2
  160. notionary/utils/decorators.py +6 -2
  161. notionary/utils/fuzzy.py +6 -2
  162. notionary/utils/mixins/logging.py +3 -1
  163. notionary/utils/pagination.py +14 -4
  164. notionary/workspace/__init__.py +5 -1
  165. notionary/workspace/query/service.py +59 -16
  166. notionary/workspace/service.py +39 -11
  167. {notionary-0.4.0.dist-info → notionary-0.4.1.dist-info}/METADATA +1 -1
  168. notionary-0.4.1.dist-info/RECORD +236 -0
  169. notionary/page/blocks/client.py +0 -1
  170. notionary/page/content/syntax/__init__.py +0 -5
  171. notionary/page/content/syntax/models.py +0 -66
  172. notionary/page/content/syntax/registry.py +0 -371
  173. notionary-0.4.0.dist-info/RECORD +0 -230
  174. /notionary/page/content/syntax/{grammar.py → definition/grammar.py} +0 -0
  175. {notionary-0.4.0.dist-info → notionary-0.4.1.dist-info}/WHEEL +0 -0
  176. {notionary-0.4.0.dist-info → notionary-0.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,703 @@
1
+ import json
2
+ from dataclasses import asdict
3
+
4
+ from notionary.page.content.syntax.definition.grammar import MarkdownGrammar
5
+ from notionary.page.content.syntax.definition.models import SyntaxDefinitionRegistryKey
6
+ from notionary.page.content.syntax.definition.registry import SyntaxDefinitionRegistry
7
+ from notionary.page.content.syntax.prompts.models import SyntaxPromptData
8
+
9
+
10
+ class SyntaxPromptRegistry:
11
+ def __init__(
12
+ self,
13
+ syntax_definition_registry: SyntaxDefinitionRegistry | None = None,
14
+ markdown_grammar: MarkdownGrammar | None = None,
15
+ ):
16
+ self._syntax_definition_registry = (
17
+ syntax_definition_registry or SyntaxDefinitionRegistry()
18
+ )
19
+ self._grammar = markdown_grammar or MarkdownGrammar()
20
+ self._prompts: dict[SyntaxDefinitionRegistryKey, SyntaxPromptData] = {}
21
+ self._register_defaults()
22
+
23
+ def get_prompt_data(self, key: SyntaxDefinitionRegistryKey) -> SyntaxPromptData:
24
+ return self._prompts[key]
25
+
26
+ def get_all_prompt_data_as_json(self, indent: int | None = 2) -> str:
27
+ prompts_dict = self._prompts.copy()
28
+
29
+ json_compatible_dict = {
30
+ key.value: asdict(value) for key, value in prompts_dict.items()
31
+ }
32
+
33
+ return json.dumps(json_compatible_dict, indent=indent, ensure_ascii=False)
34
+
35
+ def get_breadcrumb_prompt(self) -> SyntaxPromptData:
36
+ return self._prompts[SyntaxDefinitionRegistryKey.BREADCRUMB]
37
+
38
+ def get_bulleted_list_prompt(self) -> SyntaxPromptData:
39
+ return self._prompts[SyntaxDefinitionRegistryKey.BULLETED_LIST]
40
+
41
+ def get_divider_prompt(self) -> SyntaxPromptData:
42
+ return self._prompts[SyntaxDefinitionRegistryKey.DIVIDER]
43
+
44
+ def get_numbered_list_prompt(self) -> SyntaxPromptData:
45
+ return self._prompts[SyntaxDefinitionRegistryKey.NUMBERED_LIST]
46
+
47
+ def get_quote_prompt(self) -> SyntaxPromptData:
48
+ return self._prompts[SyntaxDefinitionRegistryKey.QUOTE]
49
+
50
+ def get_table_prompt(self) -> SyntaxPromptData:
51
+ return self._prompts[SyntaxDefinitionRegistryKey.TABLE]
52
+
53
+ def get_table_of_contents_prompt(self) -> SyntaxPromptData:
54
+ return self._prompts[SyntaxDefinitionRegistryKey.TABLE_OF_CONTENTS]
55
+
56
+ def get_todo_prompt(self) -> SyntaxPromptData:
57
+ return self._prompts[SyntaxDefinitionRegistryKey.TO_DO]
58
+
59
+ def get_todo_done_prompt(self) -> SyntaxPromptData:
60
+ return self._prompts[SyntaxDefinitionRegistryKey.TO_DO_DONE]
61
+
62
+ def get_caption_prompt(self) -> SyntaxPromptData:
63
+ return self._prompts[SyntaxDefinitionRegistryKey.CAPTION]
64
+
65
+ def get_space_prompt(self) -> SyntaxPromptData:
66
+ return self._prompts[SyntaxDefinitionRegistryKey.SPACE]
67
+
68
+ def get_heading_prompt(self) -> SyntaxPromptData:
69
+ return self._prompts[SyntaxDefinitionRegistryKey.HEADING]
70
+
71
+ def get_audio_prompt(self) -> SyntaxPromptData:
72
+ return self._prompts[SyntaxDefinitionRegistryKey.AUDIO]
73
+
74
+ def get_bookmark_prompt(self) -> SyntaxPromptData:
75
+ return self._prompts[SyntaxDefinitionRegistryKey.BOOKMARK]
76
+
77
+ def get_embed_prompt(self) -> SyntaxPromptData:
78
+ return self._prompts[SyntaxDefinitionRegistryKey.EMBED]
79
+
80
+ def get_file_prompt(self) -> SyntaxPromptData:
81
+ return self._prompts[SyntaxDefinitionRegistryKey.FILE]
82
+
83
+ def get_image_prompt(self) -> SyntaxPromptData:
84
+ return self._prompts[SyntaxDefinitionRegistryKey.IMAGE]
85
+
86
+ def get_pdf_prompt(self) -> SyntaxPromptData:
87
+ return self._prompts[SyntaxDefinitionRegistryKey.PDF]
88
+
89
+ def get_video_prompt(self) -> SyntaxPromptData:
90
+ return self._prompts[SyntaxDefinitionRegistryKey.VIDEO]
91
+
92
+ def get_callout_prompt(self) -> SyntaxPromptData:
93
+ return self._prompts[SyntaxDefinitionRegistryKey.CALLOUT]
94
+
95
+ def get_code_prompt(self) -> SyntaxPromptData:
96
+ return self._prompts[SyntaxDefinitionRegistryKey.CODE]
97
+
98
+ def get_column_prompt(self) -> SyntaxPromptData:
99
+ return self._prompts[SyntaxDefinitionRegistryKey.COLUMN_LIST]
100
+
101
+ def get_equation_prompt(self) -> SyntaxPromptData:
102
+ return self._prompts[SyntaxDefinitionRegistryKey.EQUATION]
103
+
104
+ def get_toggle_prompt(self) -> SyntaxPromptData:
105
+ return self._prompts[SyntaxDefinitionRegistryKey.TOGGLE]
106
+
107
+ def _get_nesting_info(self) -> str:
108
+ return f"Can be nested by indenting with {self._grammar.spaces_per_nesting_level} spaces per level."
109
+
110
+ def _get_media_path_usage_notes(self) -> str:
111
+ return (
112
+ "Accepts both URLs (starting with http:// or https://) and local file paths. "
113
+ "For local files, use relative or absolute paths WITHOUT http/https prefix. "
114
+ "Examples: 'images/photo.jpg' or '/home/user/document.pdf'. "
115
+ "Do NOT prefix local file paths with http:// or https://."
116
+ )
117
+
118
+ def _generate_url_media_examples(
119
+ self, start_delimiter: str, end_delimiter: str, example_urls: list[str]
120
+ ) -> list[str]:
121
+ return [f"{start_delimiter}{url}{end_delimiter}" for url in example_urls]
122
+
123
+ def _register_defaults(self) -> None:
124
+ self._register_audio_prompt()
125
+ self._register_video_prompt()
126
+ self._register_image_prompt()
127
+ self._register_file_prompt()
128
+ self._register_pdf_prompt()
129
+ self._register_bookmark_prompt()
130
+ self._register_embed_prompt()
131
+
132
+ self._register_bulleted_list_prompt()
133
+ self._register_numbered_list_prompt()
134
+ self._register_todo_prompt()
135
+ self._register_todo_done_prompt()
136
+
137
+ self._register_toggle_prompt()
138
+ self._register_callout_prompt()
139
+ self._register_code_prompt()
140
+ self._register_column_prompt()
141
+ self._register_equation_prompt()
142
+
143
+ self._register_quote_prompt()
144
+ self._register_heading_prompt()
145
+ self._register_divider_prompt()
146
+ self._register_breadcrumb_prompt()
147
+ self._register_table_of_contents_prompt()
148
+ self._register_table_prompt()
149
+ self._register_caption_prompt()
150
+ self._register_space_prompt()
151
+
152
+ # Media elements
153
+ def _register_audio_prompt(self) -> None:
154
+ definition = self._syntax_definition_registry.get_audio_syntax()
155
+ self._prompts[SyntaxDefinitionRegistryKey.AUDIO] = SyntaxPromptData(
156
+ element="Audio",
157
+ description="Embeds an audio file into the page. Supports various audio formats like MP3, WAV, OGG.",
158
+ is_multi_line=False,
159
+ few_shot_examples=self._generate_audio_examples(
160
+ definition.start_delimiter, definition.end_delimiter
161
+ ),
162
+ usage_notes=self._get_media_path_usage_notes(),
163
+ supports_inline_rich_text=False,
164
+ )
165
+
166
+ def _generate_audio_examples(
167
+ self, start_delimiter: str, end_delimiter: str
168
+ ) -> list[str]:
169
+ """Generate valid audio examples with real audio file extensions."""
170
+ return [
171
+ f"{start_delimiter}https://example.com/audio.mp3{end_delimiter}",
172
+ f"{start_delimiter}https://example.com/sound.wav{end_delimiter}",
173
+ f"{start_delimiter}https://example.com/music.ogg{end_delimiter}",
174
+ ]
175
+
176
+ def _register_video_prompt(self) -> None:
177
+ definition = self._syntax_definition_registry.get_video_syntax()
178
+ self._prompts[SyntaxDefinitionRegistryKey.VIDEO] = SyntaxPromptData(
179
+ element="Video",
180
+ description="Embeds a video file into the page. Supports various video formats like MP4, WebM, AVI.",
181
+ is_multi_line=False,
182
+ few_shot_examples=self._generate_video_examples(
183
+ definition.start_delimiter, definition.end_delimiter
184
+ ),
185
+ usage_notes=self._get_media_path_usage_notes(),
186
+ supports_inline_rich_text=False,
187
+ )
188
+
189
+ def _generate_video_examples(
190
+ self, start_delimiter: str, end_delimiter: str
191
+ ) -> list[str]:
192
+ """Generate valid video examples with real video file extensions."""
193
+ return [
194
+ f"{start_delimiter}https://example.com/video.mp4{end_delimiter}",
195
+ f"{start_delimiter}https://example.com/clip.mov{end_delimiter}",
196
+ f"{start_delimiter}https://www.youtube.com/watch?v=dQw4w9WgXcQ{end_delimiter}",
197
+ ]
198
+
199
+ def _register_image_prompt(self) -> None:
200
+ definition = self._syntax_definition_registry.get_image_syntax()
201
+ self._prompts[SyntaxDefinitionRegistryKey.IMAGE] = SyntaxPromptData(
202
+ element="Image",
203
+ description="Embeds an image into the page. Supports formats like PNG, JPG, GIF, WebP.",
204
+ is_multi_line=False,
205
+ few_shot_examples=self._generate_image_examples(
206
+ definition.start_delimiter, definition.end_delimiter
207
+ ),
208
+ usage_notes=self._get_media_path_usage_notes(),
209
+ supports_inline_rich_text=False,
210
+ )
211
+
212
+ def _generate_image_examples(
213
+ self, start_delimiter: str, end_delimiter: str
214
+ ) -> list[str]:
215
+ """Generate valid image examples with real image file extensions."""
216
+ return [
217
+ f"{start_delimiter}https://example.com/photo.jpg{end_delimiter}",
218
+ f"{start_delimiter}https://example.com/image.png{end_delimiter}",
219
+ f"{start_delimiter}https://example.com/graphic.webp{end_delimiter}",
220
+ ]
221
+
222
+ def _register_file_prompt(self) -> None:
223
+ definition = self._syntax_definition_registry.get_file_syntax()
224
+ self._prompts[SyntaxDefinitionRegistryKey.FILE] = SyntaxPromptData(
225
+ element="File",
226
+ description="Links to a downloadable file. Can be used for any file type.",
227
+ is_multi_line=False,
228
+ few_shot_examples=self._generate_file_examples(
229
+ definition.start_delimiter, definition.end_delimiter
230
+ ),
231
+ usage_notes=self._get_media_path_usage_notes(),
232
+ supports_inline_rich_text=False,
233
+ )
234
+
235
+ def _generate_file_examples(
236
+ self, start_delimiter: str, end_delimiter: str
237
+ ) -> list[str]:
238
+ """Generate valid file examples with various file extensions."""
239
+ return [
240
+ f"{start_delimiter}https://example.com/document.docx{end_delimiter}",
241
+ f"{start_delimiter}https://example.com/archive.zip{end_delimiter}",
242
+ f"{start_delimiter}https://example.com/data.csv{end_delimiter}",
243
+ ]
244
+
245
+ def _register_pdf_prompt(self) -> None:
246
+ definition = self._syntax_definition_registry.get_pdf_syntax()
247
+ self._prompts[SyntaxDefinitionRegistryKey.PDF] = SyntaxPromptData(
248
+ element="PDF",
249
+ description="Embeds a PDF document into the page for inline viewing.",
250
+ is_multi_line=False,
251
+ few_shot_examples=self._generate_pdf_examples(
252
+ definition.start_delimiter, definition.end_delimiter
253
+ ),
254
+ usage_notes=self._get_media_path_usage_notes(),
255
+ supports_inline_rich_text=False,
256
+ )
257
+
258
+ def _generate_pdf_examples(
259
+ self, start_delimiter: str, end_delimiter: str
260
+ ) -> list[str]:
261
+ """Generate valid PDF examples."""
262
+ return [
263
+ f"{start_delimiter}https://example.com/document.pdf{end_delimiter}",
264
+ f"{start_delimiter}https://example.com/manual.pdf{end_delimiter}",
265
+ f"{start_delimiter}https://example.com/report.pdf{end_delimiter}",
266
+ ]
267
+
268
+ def _register_bookmark_prompt(self) -> None:
269
+ definition = self._syntax_definition_registry.get_bookmark_syntax()
270
+ self._prompts[SyntaxDefinitionRegistryKey.BOOKMARK] = SyntaxPromptData(
271
+ element="Bookmark",
272
+ description="Creates a bookmark link to an external URL. Only accepts HTTP/HTTPS URLs.",
273
+ is_multi_line=False,
274
+ few_shot_examples=self._generate_url_media_examples(
275
+ definition.start_delimiter,
276
+ definition.end_delimiter,
277
+ [
278
+ "https://example.com",
279
+ "https://github.com/project",
280
+ "https://docs.example.com/guide",
281
+ ],
282
+ ),
283
+ usage_notes=(
284
+ "Only accepts HTTP/HTTPS URLs. "
285
+ "Use the full URL including the protocol. "
286
+ "Cannot reference local files."
287
+ ),
288
+ supports_inline_rich_text=False,
289
+ )
290
+
291
+ def _register_embed_prompt(self) -> None:
292
+ definition = self._syntax_definition_registry.get_embed_syntax()
293
+ self._prompts[SyntaxDefinitionRegistryKey.EMBED] = SyntaxPromptData(
294
+ element="Embed",
295
+ description="Embeds external content (like YouTube videos, tweets, etc.) into the page.",
296
+ is_multi_line=False,
297
+ few_shot_examples=self._generate_url_media_examples(
298
+ definition.start_delimiter,
299
+ definition.end_delimiter,
300
+ [
301
+ "https://www.youtube.com/watch?v=dQw4w9WgXcQ",
302
+ "https://twitter.com/user/status/123456789",
303
+ "https://open.spotify.com/track/xyz",
304
+ ],
305
+ ),
306
+ usage_notes=(
307
+ "Only accepts HTTP/HTTPS URLs from supported platforms. "
308
+ "Use the full shareable URL from the platform."
309
+ ),
310
+ supports_inline_rich_text=False,
311
+ )
312
+
313
+ # Lists
314
+ def _register_bulleted_list_prompt(self) -> None:
315
+ definition = self._syntax_definition_registry.get_bulleted_list_syntax()
316
+ spaces = " " * self._grammar.spaces_per_nesting_level
317
+ self._prompts[SyntaxDefinitionRegistryKey.BULLETED_LIST] = SyntaxPromptData(
318
+ element="Bulleted List",
319
+ description=f"Creates a bulleted (unordered) list item. {self._get_nesting_info()}",
320
+ is_multi_line=False,
321
+ few_shot_examples=[
322
+ f"{definition.start_delimiter}First item",
323
+ f"{definition.start_delimiter}Second item",
324
+ f"{spaces}{definition.start_delimiter}Nested item (indented with {self._grammar.spaces_per_nesting_level} spaces)",
325
+ ],
326
+ usage_notes=(
327
+ f"Each list item is on its own line. "
328
+ f"Nest items by indenting with exactly {self._grammar.spaces_per_nesting_level} spaces per level. "
329
+ f"The content supports inline formatting like **bold**, *italic*, `code`, and links."
330
+ ),
331
+ supports_inline_rich_text=True,
332
+ )
333
+
334
+ def _register_numbered_list_prompt(self) -> None:
335
+ definition = self._syntax_definition_registry.get_numbered_list_syntax()
336
+ spaces = " " * self._grammar.spaces_per_nesting_level
337
+ self._prompts[SyntaxDefinitionRegistryKey.NUMBERED_LIST] = SyntaxPromptData(
338
+ element="Numbered List",
339
+ description=f"Creates a numbered (ordered) list item. {self._get_nesting_info()}",
340
+ is_multi_line=False,
341
+ few_shot_examples=[
342
+ f"{definition.start_delimiter}First step",
343
+ "2. Second step",
344
+ f"{spaces}1. Sub-step (indented with {self._grammar.spaces_per_nesting_level} spaces)",
345
+ ],
346
+ usage_notes=(
347
+ f"Each list item is on its own line. "
348
+ f"The number is automatically incremented. "
349
+ f"Nest items by indenting with exactly {self._grammar.spaces_per_nesting_level} spaces per level. "
350
+ f"The content supports inline formatting like **bold**, *italic*, `code`, and links."
351
+ ),
352
+ supports_inline_rich_text=True,
353
+ )
354
+
355
+ def _register_todo_prompt(self) -> None:
356
+ definition = self._syntax_definition_registry.get_todo_syntax()
357
+ self._prompts[SyntaxDefinitionRegistryKey.TO_DO] = SyntaxPromptData(
358
+ element="Todo",
359
+ description=f"Creates an unchecked todo item. {self._get_nesting_info()}",
360
+ is_multi_line=False,
361
+ few_shot_examples=[
362
+ f"{definition.start_delimiter} Task to complete",
363
+ f"{definition.start_delimiter} Buy groceries",
364
+ f"{definition.start_delimiter} Write documentation",
365
+ ],
366
+ usage_notes=(
367
+ f"Each todo item is on its own line. Can be nested by indenting with {self._grammar.spaces_per_nesting_level} spaces. The content supports inline formatting."
368
+ ),
369
+ supports_inline_rich_text=True,
370
+ )
371
+
372
+ def _register_todo_done_prompt(self) -> None:
373
+ definition = self._syntax_definition_registry.get_todo_done_syntax()
374
+ self._prompts[SyntaxDefinitionRegistryKey.TO_DO_DONE] = SyntaxPromptData(
375
+ element="Todo Done",
376
+ description=f"Creates a checked (completed) todo item. {self._get_nesting_info()}",
377
+ is_multi_line=False,
378
+ few_shot_examples=[
379
+ f"{definition.start_delimiter} Completed task",
380
+ f"{definition.start_delimiter} Fixed the bug",
381
+ f"{definition.start_delimiter} Deployed to production",
382
+ ],
383
+ usage_notes=(
384
+ f"Each completed todo item is on its own line. "
385
+ f"Can be nested by indenting with {self._grammar.spaces_per_nesting_level} spaces. "
386
+ f"The content supports inline formatting."
387
+ ),
388
+ supports_inline_rich_text=True,
389
+ )
390
+
391
+ # Block containers
392
+ def _register_toggle_prompt(self) -> None:
393
+ delimiter = self._grammar.toggle_delimiter
394
+ spaces = " " * self._grammar.spaces_per_nesting_level
395
+ self._prompts[SyntaxDefinitionRegistryKey.TOGGLE] = SyntaxPromptData(
396
+ element="Toggle",
397
+ description="Creates a toggleable/collapsible section with a title. Content between delimiters can be shown/hidden.",
398
+ is_multi_line=True,
399
+ few_shot_examples=[
400
+ (
401
+ f"{delimiter} Click to expand\n"
402
+ f"{spaces}Hidden content here\n"
403
+ f"{spaces}More hidden content"
404
+ ),
405
+ (
406
+ f"{delimiter} FAQ Answer\n"
407
+ f"{spaces}Detailed explanation...\n"
408
+ f"{spaces}Additional details"
409
+ ),
410
+ (
411
+ f"{delimiter} Show more details\n"
412
+ f"{spaces}Additional information\n"
413
+ f"{spaces}Even more content"
414
+ ),
415
+ ],
416
+ usage_notes=(
417
+ f"The toggle title (first line after {delimiter}) starts the collapsible section. "
418
+ f"Content on subsequent lines indented by {self._grammar.spaces_per_nesting_level} spaces becomes children of the toggle. "
419
+ f"The toggle ends when indentation returns to the original level. "
420
+ f"The title supports inline formatting."
421
+ ),
422
+ supports_inline_rich_text=True,
423
+ )
424
+
425
+ def _register_callout_prompt(self) -> None:
426
+ definition = self._syntax_definition_registry.get_callout_syntax()
427
+ self._prompts[SyntaxDefinitionRegistryKey.CALLOUT] = SyntaxPromptData(
428
+ element="Callout",
429
+ description=f"Creates a highlighted callout box with optional emoji/icon and title. Children must be indented by {self._grammar.spaces_per_nesting_level} spaces.",
430
+ is_multi_line=False,
431
+ few_shot_examples=[
432
+ f'{definition.start_delimiter}(💡 "Pro Tip")',
433
+ f'{definition.start_delimiter}(⚠️ "Warning")',
434
+ f'{definition.start_delimiter}(📌 "Important Note")',
435
+ ],
436
+ usage_notes=(
437
+ f"The callout declaration is single-line, but can contain nested content on "
438
+ f"subsequent lines indented by {self._grammar.spaces_per_nesting_level} spaces. "
439
+ f"Format: emoji/icon followed by optional title in quotes. "
440
+ f"The title supports inline formatting."
441
+ ),
442
+ supports_inline_rich_text=True,
443
+ )
444
+
445
+ def _register_code_prompt(self) -> None:
446
+ delimiter = "```"
447
+ self._prompts[SyntaxDefinitionRegistryKey.CODE] = SyntaxPromptData(
448
+ element="Code",
449
+ description="Creates a code block with optional syntax highlighting. Specify language after opening delimiter.",
450
+ is_multi_line=True,
451
+ few_shot_examples=[
452
+ f"{delimiter}python\nprint('Hello, World!')\n{delimiter}",
453
+ f"{delimiter}javascript\nconsole.log('Hello!');\n{delimiter}",
454
+ f"{delimiter}\nPlain text code\n{delimiter}",
455
+ ],
456
+ usage_notes=(
457
+ "Language identifier is optional but recommended for syntax highlighting. "
458
+ "Content between delimiters is preserved exactly as written, including whitespace. "
459
+ "NO inline formatting is supported inside code blocks."
460
+ ),
461
+ supports_inline_rich_text=False,
462
+ )
463
+
464
+ def _register_column_prompt(self) -> None:
465
+ delimiter = self._grammar.column_delimiter
466
+ spaces = " " * self._grammar.spaces_per_nesting_level
467
+ self._prompts[SyntaxDefinitionRegistryKey.COLUMN_LIST] = SyntaxPromptData(
468
+ element="Columns",
469
+ description="Creates a multi-column layout. Requires at least 2 columns. Columns are defined by indentation.",
470
+ is_multi_line=True,
471
+ few_shot_examples=[
472
+ (
473
+ f"{delimiter} columns\n"
474
+ f"{spaces}{delimiter} column\n"
475
+ f"{spaces * 2}First column content\n"
476
+ f"{spaces}{delimiter} column\n"
477
+ f"{spaces * 2}Second column content\n"
478
+ ),
479
+ (
480
+ f"{delimiter} columns\n"
481
+ f"{spaces}{delimiter} column 0.6\n"
482
+ f"{spaces * 2}Wide column (60%)\n"
483
+ f"{spaces * 2}More content here\n"
484
+ f"{spaces}{delimiter} column 0.4\n"
485
+ f"{spaces * 2}Narrow column (40%)\n"
486
+ f"{spaces * 2}Content here\n"
487
+ ),
488
+ (
489
+ f"{delimiter} columns\n"
490
+ f"{spaces}{delimiter} column\n"
491
+ f"{spaces * 2}Left column\n"
492
+ f"{spaces}{delimiter} column\n"
493
+ f"{spaces * 2}Middle column\n"
494
+ f"{spaces}{delimiter} column\n"
495
+ f"{spaces * 2}Right column\n"
496
+ ),
497
+ ],
498
+ usage_notes=(
499
+ f"Start with '{delimiter} columns' line. "
500
+ f"Each column is defined with '{delimiter} column' indented by {self._grammar.spaces_per_nesting_level} spaces. "
501
+ f"Column content must be indented by an additional {self._grammar.spaces_per_nesting_level} spaces (total {self._grammar.spaces_per_nesting_level * 2} spaces from start). "
502
+ f"Optional width ratio (0.0-1.0) can be specified after 'column'. "
503
+ f"At least 2 columns are required. "
504
+ f"The column list ends when indentation returns to the original level. "
505
+ f"Column content supports all other block elements and inline formatting."
506
+ ),
507
+ supports_inline_rich_text=True,
508
+ )
509
+
510
+ def _register_equation_prompt(self) -> None:
511
+ delimiter = "$$"
512
+ self._prompts[SyntaxDefinitionRegistryKey.EQUATION] = SyntaxPromptData(
513
+ element="Equation",
514
+ description="Creates a mathematical equation block using LaTeX syntax.",
515
+ is_multi_line=True,
516
+ few_shot_examples=[
517
+ f"{delimiter}\nE = mc^2\n{delimiter}",
518
+ f"{delimiter}\n\\frac{{-b \\pm \\sqrt{{b^2-4ac}}}}{{2a}}\n{delimiter}",
519
+ f"{delimiter}\n\\sum_{{i=1}}^{{n}} i = \\frac{{n(n+1)}}{{2}}\n{delimiter}",
520
+ ],
521
+ usage_notes=(
522
+ "Uses LaTeX syntax for mathematical notation. "
523
+ "Content between delimiters is rendered as LaTeX. "
524
+ "NO markdown formatting is supported, only LaTeX commands."
525
+ ),
526
+ supports_inline_rich_text=False,
527
+ )
528
+
529
+ # Text blocks
530
+ def _register_quote_prompt(self) -> None:
531
+ definition = self._syntax_definition_registry.get_quote_syntax()
532
+ self._prompts[SyntaxDefinitionRegistryKey.QUOTE] = SyntaxPromptData(
533
+ element="Quote",
534
+ description=f"Creates a block quote for citing text or highlighting quotations. {self._get_nesting_info()}",
535
+ is_multi_line=False,
536
+ few_shot_examples=[
537
+ f"{definition.start_delimiter}Single line quote",
538
+ f"{definition.start_delimiter}First line\n{definition.start_delimiter}Second line\n{definition.start_delimiter}Third line",
539
+ f"{definition.start_delimiter}Multi-paragraph quotes need multiple quote markers",
540
+ ],
541
+ usage_notes=(
542
+ f"While marked as single-line syntax, quotes can span multiple lines by "
543
+ f"prefixing each line with '{definition.start_delimiter}'. "
544
+ f"Each line needs its own '{definition.start_delimiter}' prefix but they will "
545
+ f"render as a continuous quote block. "
546
+ f"The content supports inline formatting like **bold** and *italic*."
547
+ ),
548
+ supports_inline_rich_text=True,
549
+ )
550
+
551
+ def _register_heading_prompt(self) -> None:
552
+ spaces = " " * self._grammar.spaces_per_nesting_level
553
+ self._prompts[SyntaxDefinitionRegistryKey.HEADING] = SyntaxPromptData(
554
+ element="Heading",
555
+ description="Creates a heading. Use 1-3 # symbols for different heading levels. Becomes toggleable when followed by indented content.",
556
+ is_multi_line=False,
557
+ few_shot_examples=[
558
+ "# Heading 1 (largest)",
559
+ "## Heading 2 (medium)",
560
+ "### Heading 3 (smallest)",
561
+ (
562
+ f"## Toggleable Heading\n"
563
+ f"{spaces}This content is indented by {self._grammar.spaces_per_nesting_level} spaces\n"
564
+ f"{spaces}So it becomes a child of the heading\n"
565
+ f"{spaces}Making the heading toggleable\n"
566
+ f"\n"
567
+ f"Next paragraph ends the block"
568
+ ),
569
+ ],
570
+ usage_notes=(
571
+ f"Use # for top-level headings, ## for subsections, and ### for sub-subsections. "
572
+ f"The heading text supports inline formatting like **bold**, *italic*, and `code`. "
573
+ f"To make a heading toggleable, indent the following content by {self._grammar.spaces_per_nesting_level} spaces. "
574
+ f"All indented content becomes collapsible children of the heading. "
575
+ f"An empty line or content without indentation ends the toggleable block."
576
+ ),
577
+ supports_inline_rich_text=True,
578
+ )
579
+
580
+ def _register_divider_prompt(self) -> None:
581
+ definition = self._syntax_definition_registry.get_divider_syntax()
582
+ self._prompts[SyntaxDefinitionRegistryKey.DIVIDER] = SyntaxPromptData(
583
+ element="Divider",
584
+ description="Creates a horizontal divider line to separate content sections.",
585
+ is_multi_line=False,
586
+ few_shot_examples=[
587
+ f"{definition.start_delimiter}",
588
+ "----",
589
+ "-----",
590
+ ],
591
+ usage_notes=(
592
+ "Use at least three dashes. "
593
+ "The divider is purely visual and contains no text content."
594
+ ),
595
+ supports_inline_rich_text=False,
596
+ )
597
+
598
+ def _register_breadcrumb_prompt(self) -> None:
599
+ definition = self._syntax_definition_registry.get_breadcrumb_syntax()
600
+ self._prompts[SyntaxDefinitionRegistryKey.BREADCRUMB] = SyntaxPromptData(
601
+ element="Breadcrumb",
602
+ description="Creates a breadcrumb navigation element showing the current page hierarchy.",
603
+ is_multi_line=False,
604
+ few_shot_examples=[
605
+ f"{definition.start_delimiter}",
606
+ ],
607
+ usage_notes=(
608
+ "Automatically generates breadcrumb navigation based on page hierarchy. "
609
+ "No additional parameters or content needed."
610
+ ),
611
+ supports_inline_rich_text=False,
612
+ )
613
+
614
+ def _register_table_of_contents_prompt(self) -> None:
615
+ self._prompts[SyntaxDefinitionRegistryKey.TABLE_OF_CONTENTS] = SyntaxPromptData(
616
+ element="Table of Contents",
617
+ description="Generates a table of contents based on the headings in the document.",
618
+ is_multi_line=False,
619
+ few_shot_examples=[
620
+ "[toc]",
621
+ "[TOC]",
622
+ ],
623
+ usage_notes=(
624
+ "Automatically generates a table of contents from all headings in the document. "
625
+ "Case-insensitive. No additional parameters needed."
626
+ ),
627
+ supports_inline_rich_text=False,
628
+ )
629
+
630
+ def _register_table_prompt(self) -> None:
631
+ delimiter = self._grammar.table_delimiter
632
+ self._prompts[SyntaxDefinitionRegistryKey.TABLE] = SyntaxPromptData(
633
+ element="Table",
634
+ description=(
635
+ "Creates a table with headers, separator row, and data rows. Cells are separated by delimiters."
636
+ ),
637
+ is_multi_line=False,
638
+ few_shot_examples=[
639
+ (
640
+ f"{delimiter}Header 1{delimiter}Header 2{delimiter}Header 3{delimiter}\n"
641
+ f"{delimiter}---{delimiter}---{delimiter}---{delimiter}\n"
642
+ f"{delimiter}Data 1{delimiter}Data 2{delimiter}Data 3{delimiter}\n"
643
+ f"{delimiter}More 1{delimiter}More 2{delimiter}More 3{delimiter}"
644
+ ),
645
+ (
646
+ f"{delimiter}Name{delimiter}Age{delimiter}City{delimiter}\n"
647
+ f"{delimiter}:---{delimiter}:---:{delimiter}---:{delimiter}\n"
648
+ f"{delimiter}Alice{delimiter}30{delimiter}NYC{delimiter}\n"
649
+ f"{delimiter}Bob{delimiter}25{delimiter}LA{delimiter}"
650
+ ),
651
+ (
652
+ f"{delimiter}Product{delimiter}Price{delimiter}Stock{delimiter}\n"
653
+ f"{delimiter}-{delimiter}-{delimiter}-{delimiter}\n"
654
+ f"{delimiter}Widget{delimiter}$10{delimiter}50{delimiter}\n"
655
+ f"{delimiter}Gadget{delimiter}$25{delimiter}30{delimiter}"
656
+ ),
657
+ ],
658
+ usage_notes=(
659
+ f"A table consists of: "
660
+ f"(1) Header row with column names, "
661
+ f"(2) Separator row with dashes "
662
+ f"(use ':---' for left-align, ':---:' for center-align, '---:' for right-align), "
663
+ f"(3) One or more data rows. "
664
+ f"Each row is on its own line. "
665
+ f"Cells are separated by '{delimiter}'. "
666
+ f"The cell content supports inline formatting like **bold** and *italic*."
667
+ ),
668
+ supports_inline_rich_text=True,
669
+ )
670
+
671
+ def _register_caption_prompt(self) -> None:
672
+ definition = self._syntax_definition_registry.get_caption_syntax()
673
+ self._prompts[SyntaxDefinitionRegistryKey.CAPTION] = SyntaxPromptData(
674
+ element="Caption",
675
+ description="Adds a caption to the preceding element (like an image or table).",
676
+ is_multi_line=False,
677
+ few_shot_examples=[
678
+ f"{definition.start_delimiter} Figure 1: Architecture diagram",
679
+ f"{definition.start_delimiter} Table showing quarterly results",
680
+ f"{definition.start_delimiter} Screenshot of the interface",
681
+ ],
682
+ usage_notes=(
683
+ "Place immediately after the element you want to caption (image, table, etc.). "
684
+ "The caption text supports inline formatting."
685
+ ),
686
+ supports_inline_rich_text=True,
687
+ )
688
+
689
+ def _register_space_prompt(self) -> None:
690
+ definition = self._syntax_definition_registry.get_space_syntax()
691
+ self._prompts[SyntaxDefinitionRegistryKey.SPACE] = SyntaxPromptData(
692
+ element="Space",
693
+ description="Inserts a blank space/line for visual separation.",
694
+ is_multi_line=False,
695
+ few_shot_examples=[
696
+ f"{definition.start_delimiter}",
697
+ ],
698
+ usage_notes=(
699
+ "Creates vertical whitespace between elements. "
700
+ "No content or parameters needed."
701
+ ),
702
+ supports_inline_rich_text=False,
703
+ )