markdown-flow 0.2.81__tar.gz → 0.2.83__tar.gz

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 (51) hide show
  1. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/PKG-INFO +2 -2
  2. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/README.md +1 -1
  3. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/__init__.py +1 -1
  4. markdown_flow-0.2.83/markdown_flow/constants.py +254 -0
  5. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/core.py +91 -6
  6. markdown_flow-0.2.83/markdown_flow/system_prompt.md +140 -0
  7. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow.egg-info/PKG-INFO +2 -2
  8. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow.egg-info/SOURCES.txt +1 -12
  9. markdown_flow-0.2.81/markdown_flow/constants.py +0 -239
  10. markdown_flow-0.2.81/markdown_flow/system_prompt.md +0 -140
  11. markdown_flow-0.2.81/tests/test_dynamic_interaction.py +0 -936
  12. markdown_flow-0.2.81/tests/test_error_render_context.py +0 -58
  13. markdown_flow-0.2.81/tests/test_formatter.py +0 -372
  14. markdown_flow-0.2.81/tests/test_formatter_stream.py +0 -277
  15. markdown_flow-0.2.81/tests/test_html_comment_utils.py +0 -148
  16. markdown_flow-0.2.81/tests/test_markdownflow_basic.py +0 -323
  17. markdown_flow-0.2.81/tests/test_parser_interaction.py +0 -124
  18. markdown_flow-0.2.81/tests/test_parser_output.py +0 -260
  19. markdown_flow-0.2.81/tests/test_parser_variable.py +0 -183
  20. markdown_flow-0.2.81/tests/test_preprocessor.py +0 -494
  21. markdown_flow-0.2.81/tests/test_preserved_simple.py +0 -262
  22. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/LICENSE +0 -0
  23. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/constants_system_prompt.py +0 -0
  24. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/enums.py +0 -0
  25. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/exceptions.py +0 -0
  26. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/formatter/__init__.py +0 -0
  27. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/formatter/classifier.py +0 -0
  28. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/formatter/format.py +0 -0
  29. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/formatter/patterns.py +0 -0
  30. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/formatter/stream.py +0 -0
  31. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/formatter/types.py +0 -0
  32. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/llm.py +0 -0
  33. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/models.py +0 -0
  34. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/__init__.py +0 -0
  35. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/code_fence_utils.py +0 -0
  36. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/html_comment_utils.py +0 -0
  37. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/interaction.py +0 -0
  38. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/json_parser.py +0 -0
  39. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/output.py +0 -0
  40. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/preprocessor.py +0 -0
  41. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/validation.py +0 -0
  42. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/parser/variable.py +0 -0
  43. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/providers/__init__.py +0 -0
  44. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/providers/config.py +0 -0
  45. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/providers/openai.py +0 -0
  46. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/tag_filter.py +0 -0
  47. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow/utils.py +0 -0
  48. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow.egg-info/dependency_links.txt +0 -0
  49. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/markdown_flow.egg-info/top_level.txt +0 -0
  50. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/pyproject.toml +0 -0
  51. {markdown_flow-0.2.81 → markdown_flow-0.2.83}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: markdown-flow
3
- Version: 0.2.81
3
+ Version: 0.2.83
4
4
  Summary: An agent library designed to parse and process MarkdownFlow documents
5
5
  Project-URL: Homepage, https://github.com/ai-shifu/markdown-flow-agent-py
6
6
  Project-URL: Bug Tracker, https://github.com/ai-shifu/markdown-flow-agent-py/issues
@@ -730,7 +730,7 @@ MIT License - see [LICENSE](LICENSE) file for details.
730
730
  - [Ruff](https://docs.astral.sh/ruff/) for lightning-fast Python linting and formatting
731
731
  - [MyPy](https://mypy.readthedocs.io/) for static type checking
732
732
  - [Commitizen](https://commitizen-tools.github.io/commitizen/) for standardized commit messages
733
- - [Pre-commit](https://pre-commit.com/) for automated code quality checks
733
+ - [Lefthook](https://lefthook.dev/) for automated code quality checks (git hooks + CI)
734
734
 
735
735
  ## 📞 Support
736
736
 
@@ -712,7 +712,7 @@ MIT License - see [LICENSE](LICENSE) file for details.
712
712
  - [Ruff](https://docs.astral.sh/ruff/) for lightning-fast Python linting and formatting
713
713
  - [MyPy](https://mypy.readthedocs.io/) for static type checking
714
714
  - [Commitizen](https://commitizen-tools.github.io/commitizen/) for standardized commit messages
715
- - [Pre-commit](https://pre-commit.com/) for automated code quality checks
715
+ - [Lefthook](https://lefthook.dev/) for automated code quality checks (git hooks + CI)
716
716
 
717
717
  ## 📞 Support
718
718
 
@@ -88,4 +88,4 @@ __all__ = [
88
88
  "replace_variables_in_text",
89
89
  ]
90
90
 
91
- __version__ = "0.2.81"
91
+ __version__ = "0.2.83"
@@ -0,0 +1,254 @@
1
+ """
2
+ Markdown-Flow Constants
3
+
4
+ Constants for document parsing, variable matching, validation, and other core functionality.
5
+ """
6
+
7
+ import re
8
+
9
+
10
+ # Pre-compiled regex patterns
11
+ COMPILED_PERCENT_VARIABLE_REGEX = re.compile(
12
+ r"%\{\{([^}]+)\}\}" # Match %{{variable}} format for preserved variables
13
+ )
14
+
15
+ # Interaction regex base patterns
16
+ INTERACTION_PATTERN = r"(?<!\\)\?\[([^\]]*)\](?!\()" # Base pattern with capturing group for content extraction, excludes escaped \?[]
17
+ INTERACTION_PATTERN_NON_CAPTURING = r"(?<!\\)\?\[[^\]]*\](?!\()" # Non-capturing version for block splitting, excludes escaped \?[]
18
+ INTERACTION_PATTERN_SPLIT = r"((?<!\\)\?\[[^\]]*\](?!\())" # Pattern for re.split() with outer capturing group, excludes escaped \?[]
19
+
20
+ # InteractionParser specific regex patterns
21
+ COMPILED_INTERACTION_REGEX = re.compile(INTERACTION_PATTERN) # Main interaction pattern matcher
22
+ COMPILED_LAYER1_INTERACTION_REGEX = COMPILED_INTERACTION_REGEX # Layer 1: Basic format validation (alias)
23
+ COMPILED_LAYER2_VARIABLE_REGEX = re.compile(r"^%\{\{([^}]+)\}\}(.*)$") # Layer 2: Variable detection
24
+ COMPILED_LAYER3_ELLIPSIS_REGEX = re.compile(r"^(.*)\.\.\.(.*)") # Layer 3: Split content around ellipsis
25
+ COMPILED_LAYER3_BUTTON_VALUE_REGEX = re.compile(r"^(.+)//(.+)$") # Layer 3: Parse Button//value format
26
+ COMPILED_BRACE_VARIABLE_REGEX = re.compile(
27
+ r"(?<!%)\{\{([^}]+)\}\}" # Match {{variable}} format for replaceable variables
28
+ )
29
+ COMPILED_SINGLE_PIPE_SPLIT_REGEX = re.compile(r"(?<!\|)\|(?!\|)") # Split on single | but not ||
30
+
31
+ # Document parsing constants (using shared INTERACTION_PATTERN defined above)
32
+
33
+ # Separators
34
+ BLOCK_SEPARATOR = r"\n\s*---\s*\n"
35
+ # Multiline preserved block fence: starts with '!' followed by 3 or more '='
36
+ PRESERVE_FENCE_PATTERN = r"^!={3,}\s*$"
37
+ COMPILED_PRESERVE_FENCE_REGEX = re.compile(PRESERVE_FENCE_PATTERN)
38
+
39
+ # Inline preserved content pattern: ===content=== format (historical compatibility)
40
+ INLINE_PRESERVE_PATTERN = r"^===(.+)=== *$"
41
+ COMPILED_INLINE_PRESERVE_REGEX = re.compile(INLINE_PRESERVE_PATTERN)
42
+
43
+ # Inline preserved content search pattern (for finding ===...=== within a line)
44
+ # Non-greedy match to handle multiple occurrences on same line
45
+ INLINE_PRESERVE_SEARCH_PATTERN = r"===\s*(.+?)\s*==="
46
+ COMPILED_INLINE_PRESERVE_SEARCH_REGEX = re.compile(INLINE_PRESERVE_SEARCH_PATTERN)
47
+
48
+ # Inline exclamation preserved content pattern: !===content!=== format (higher priority than INLINE_PRESERVE_PATTERN)
49
+ # Supports scenarios:
50
+ # - !===content!=== (compact format)
51
+ # - !=== content !=== (with spaces)
52
+ # - prefix !===content!=== suffix (inline mixed)
53
+ # - !===content\n!=== (cross-line format)
54
+ # Uses (?s) flag to make . match newlines, supports cross-line content
55
+ INLINE_EXCLAMATION_PRESERVE_PATTERN = r"(?s)!===(.*?)!==="
56
+ COMPILED_INLINE_EXCLAMATION_PRESERVE_REGEX = re.compile(INLINE_EXCLAMATION_PRESERVE_PATTERN)
57
+
58
+ # Code fence patterns (CommonMark specification compliant)
59
+ # Code block fence start: 0-3 spaces + at least 3 backticks or tildes + optional info string
60
+ CODE_FENCE_START_PATTERN = r"^[ ]{0,3}([`~]{3,})(.*)$"
61
+ COMPILED_CODE_FENCE_START_REGEX = re.compile(CODE_FENCE_START_PATTERN)
62
+
63
+ # Code block fence end: 0-3 spaces + at least 3 backticks or tildes + optional whitespace
64
+ CODE_FENCE_END_PATTERN = r"^[ ]{0,3}([`~]{3,})\s*$"
65
+ COMPILED_CODE_FENCE_END_REGEX = re.compile(CODE_FENCE_END_PATTERN)
66
+
67
+ # JSON extraction pattern for nested objects
68
+ # Matches JSON objects including nested structures using balanced braces
69
+ JSON_OBJECT_PATTERN = r"\{(?:[^{}]|(?:\{[^{}]*\}))*\}"
70
+ COMPILED_JSON_OBJECT_REGEX = re.compile(JSON_OBJECT_PATTERN)
71
+
72
+ # Output instruction markers
73
+ OUTPUT_INSTRUCTION_PREFIX = "<原样输出>"
74
+ OUTPUT_INSTRUCTION_SUFFIX = "</原样输出>"
75
+
76
+ # Output Language Control - Three-layer anchoring templates
77
+ OUTPUT_LANGUAGE_INSTRUCTION_TOP = """<output_language_override>
78
+ 🚨 CRITICAL: 100% {0} OUTPUT REQUIRED 🚨
79
+ ZERO language mixing allowed. EVERY word must be in {0}.
80
+ Before processing: Translate ALL non-{0} words/phrases to {0} first.
81
+ This overrides ALL other instructions.
82
+ </output_language_override>"""
83
+
84
+ OUTPUT_LANGUAGE_INSTRUCTION_BOTTOM = """<output_language_final_check>
85
+ 🚨 PRE-RESPONSE CHECK: Verify EVERY word is {0}. If ANY non-{0} word exists, translate it first. 🚨
86
+ </output_language_final_check>"""
87
+
88
+ # Interaction prompt templates (Modular design)
89
+ INTERACTION_PROMPT_BASE = """# JSON Interaction Translation Task
90
+
91
+ You will receive a JSON object containing interaction elements (`buttons` and/or `question` fields), and you need to translate the text in it into the target language.
92
+
93
+ ## Output Format Requirements
94
+
95
+ - Return pure JSON only; do not add any explanations or Markdown code blocks
96
+ - The JSON structure must be exactly the same as the input: do not add or remove fields, do not modify key names, and do not change the order
97
+ - Preserve the original spaces, punctuation, and quotation marks"""
98
+
99
+ INTERACTION_PROMPT_NO_TRANSLATION = """
100
+ ## Processing Rules
101
+
102
+ Return the input JSON exactly as-is, character for character:
103
+
104
+ - Do not translate any text
105
+ - Do not modify any formatting
106
+ - Do not add any content (such as display//value separation)
107
+ - Do not delete any content
108
+ - Do not change any order
109
+
110
+ ## Example
111
+
112
+ - Input `{"buttons": ["Product Manager", "Developer"], "question": "Other role"}`
113
+ - Output `{"buttons": ["Product Manager", "Developer"], "question": "Other role"}`"""
114
+
115
+ INTERACTION_PROMPT_WITH_TRANSLATION = """
116
+ ## Translation Rules
117
+
118
+ - Translate the text in `buttons` and `question` into the target language
119
+ - Translate only the display text (the Display part); do not change the JSON structure
120
+ - If `display//value` separation exists (for example `Yes//1`), translate only the Display part before `//` and keep value unchanged
121
+ - Use the target language 100%; language mixing is not allowed. Translate all non-target-language words before outputting
122
+ - If a text segment's original language is already the same as the target language, return that text unchanged (for example, when the target language is English and the original text is also English, do not rewrite it)
123
+
124
+ ## Examples
125
+
126
+ - Input `{"buttons": ["Yes//1", "No//0"]}`, target language Spanish -> `{"buttons": ["Sí//1", "No//0"]}`
127
+ - Input `{"buttons": ["Débutant", "Avancé"], "question": "Autre"}`, target language English -> `{"buttons": ["Beginner", "Advanced"], "question": "Other"}`"""
128
+
129
+ # Default: use no translation version (backward compatible)
130
+ DEFAULT_INTERACTION_PROMPT = INTERACTION_PROMPT_BASE + "\n" + INTERACTION_PROMPT_NO_TRANSLATION
131
+
132
+ # Interaction error prompt templates
133
+ DEFAULT_INTERACTION_ERROR_PROMPT = "Please rewrite the following error message to be friendlier and more personalized, helping the user understand the problem and providing constructive guidance:"
134
+
135
+ # Interaction error rendering instructions
136
+ INTERACTION_ERROR_RENDER_INSTRUCTIONS = """
137
+ Return only the friendly error message; do not include any other format or explanation."""
138
+
139
+ # Standard validation response status
140
+ VALIDATION_RESPONSE_OK = "ok"
141
+ VALIDATION_RESPONSE_ILLEGAL = "illegal"
142
+
143
+ # Validation task template (Modular design)
144
+ VALIDATION_TASK_BASE = """You are a string validation program, not a conversational assistant.
145
+
146
+ Your only task: check the input according to the following rules and output exactly one of these JSON objects:
147
+ {{"result": "ok", "parse_vars": {{"{target_variable}": "<exact user input>"}}}} or {{"result": "illegal", "reason": "<reason>"}}
148
+
149
+ It is strictly forbidden to output any natural-language explanation."""
150
+
151
+ VALIDATION_TASK_WITH_LANGUAGE = """
152
+
153
+ # reason Language Rule
154
+ reason must use the language specified in the <output_language_override> tag."""
155
+
156
+ VALIDATION_TASK_NO_LANGUAGE = """
157
+
158
+ # reason Language Rule
159
+ reason uses the primary language of the user input or question (auto-detect)."""
160
+
161
+ # Default: use no language version (backward compatible)
162
+ VALIDATION_TASK_TEMPLATE = VALIDATION_TASK_BASE + VALIDATION_TASK_NO_LANGUAGE
163
+
164
+ # Validation requirements template (extremely lenient version)
165
+ VALIDATION_REQUIREMENTS_TEMPLATE = """# Validation Algorithm (execute in order)
166
+
167
+ Step 1: Empty-value check (string length check)
168
+
169
+ Check rule: input.trim().length == 0 ?
170
+ - YES -> empty
171
+ - NO -> non-empty
172
+
173
+ Important: as long as the number of characters after trimming leading and trailing spaces is > 0, it is non-empty.
174
+ Important: do not judge semantics. All visible characters (a, 1, @, CJK characters) count toward length.
175
+ Examples:
176
+ - "" -> length 0 -> empty
177
+ - " " -> length 0 -> empty
178
+ - "aa" -> length 2 -> non-empty
179
+ - "@_@" -> length 3 -> non-empty
180
+ - "abc" -> length 3 -> non-empty
181
+
182
+ Step 2: Vague-answer check
183
+
184
+ Reject the following vague answers: "I don't know", "I'm not sure", "No / none", "I won't tell you"
185
+
186
+ Step 3: Religious/political check
187
+
188
+ Reject only explicit religious or political position statements (religious doctrines, political slogans, etc.).
189
+ Place names, regions, etc. (Beijing, Shanghai, etc.) and ordinary vocabulary do not count.
190
+
191
+ Step 4: Output result (the reason language follows the language requirement in <document_context>)
192
+
193
+ Pseudocode logic:
194
+ if empty:
195
+ output {{"result": "illegal", "reason": "input is empty (or its translation into the corresponding language)"}}
196
+ else if vague answer:
197
+ output {{"result": "illegal", "reason": "please provide specific content (or its translation into the corresponding language)"}}
198
+ else if religious/political:
199
+ output {{"result": "illegal", "reason": "contains sensitive content (or its translation into the corresponding language)"}}
200
+ else:
201
+ output {{"result": "ok", "parse_vars": {{"{target_variable}": "<exact user input>"}}}}
202
+
203
+ Extremely important:
204
+ - len(input after trimming leading and trailing spaces) > 0 -> it must be regarded as non-empty
205
+ - Symbols, numbers, brand names, place names, etc. are neither "empty" nor "invalid"
206
+ - Pass by default; reject only when there is an explicit violation
207
+ """
208
+
209
+ # ========== Error Message Constants ==========
210
+
211
+ # Interaction error messages
212
+ OPTION_SELECTION_ERROR_TEMPLATE = "请选择以下选项之一:{options}"
213
+ INPUT_EMPTY_ERROR = "输入不能为空"
214
+
215
+ # System error messages
216
+ UNSUPPORTED_PROMPT_TYPE_ERROR = "不支持的提示词类型: {prompt_type} (支持的类型: base_system, document, interaction, interaction_error, output_language)"
217
+ BLOCK_INDEX_OUT_OF_RANGE_ERROR = "Block index {index} is out of range; total={total}"
218
+ LLM_PROVIDER_REQUIRED_ERROR = "需要设置 LLMProvider 才能调用 LLM"
219
+ INTERACTION_PARSE_ERROR = "交互格式解析失败: {error}"
220
+
221
+ # LLM provider errors
222
+ NO_LLM_PROVIDER_ERROR = "NoLLMProvider 不支持 LLM 调用"
223
+
224
+ # Validation constants
225
+ JSON_PARSE_ERROR = "无法解析JSON响应"
226
+ VALIDATION_ILLEGAL_DEFAULT_REASON = "输入不合法"
227
+ VARIABLE_DEFAULT_VALUE = "UNKNOWN"
228
+
229
+ # Context generation constants
230
+ CONTEXT_QUESTION_MARKER = "# Related Question"
231
+ CONTEXT_CONVERSATION_MARKER = "# Conversation Context"
232
+ CONTEXT_BUTTON_OPTIONS_MARKER = "## Predefined Options"
233
+
234
+ # Context generation templates
235
+ CONTEXT_QUESTION_TEMPLATE = f"{CONTEXT_QUESTION_MARKER}\n{{question}}"
236
+ CONTEXT_CONVERSATION_TEMPLATE = f"{CONTEXT_CONVERSATION_MARKER}\n{{content}}"
237
+ CONTEXT_BUTTON_OPTIONS_TEMPLATE = (
238
+ f"{CONTEXT_BUTTON_OPTIONS_MARKER}\n"
239
+ "Available predefined options include: {button_options}\n"
240
+ "Note: If the user selected one of these options, it should be accepted; if the user entered custom content, accept it as long as it is a reasonable answer to the question."
241
+ )
242
+
243
+ # Next interaction context prompt templates
244
+ NEXT_INTERACTION_CONTEXT_INTRO = (
245
+ "The next interaction will appear immediately after this content. When generating the current content, connect to it naturally. "
246
+ "You may briefly restate, explain, or set up the available choices so the user understands what they will decide next, but do not output the interaction syntax or answer on the user's behalf."
247
+ )
248
+ NEXT_INTERACTION_TEXT_INPUT_TEMPLATE = "The next interaction asks the user to answer in text: {question}"
249
+ NEXT_INTERACTION_SINGLE_CHOICE_TEMPLATE = "The next interaction is a single-choice question. The user will choose one option from: {options}"
250
+ NEXT_INTERACTION_MULTIPLE_CHOICE_TEMPLATE = "The next interaction is a multiple-choice question. The user can choose one or more options from: {options}"
251
+ NEXT_INTERACTION_SINGLE_CHOICE_WITH_TEXT_INTRO = "The next interaction is a single-choice question with an optional custom text answer."
252
+ NEXT_INTERACTION_MULTIPLE_CHOICE_WITH_TEXT_INTRO = "The next interaction is a multiple-choice question with an optional custom text answer."
253
+ NEXT_INTERACTION_PREDEFINED_OPTIONS_TEMPLATE = "The predefined options are: {options}."
254
+ NEXT_INTERACTION_CUSTOM_TEXT_PROMPT_TEMPLATE = "The custom text prompt is: {question}"
@@ -25,6 +25,14 @@ from .constants import (
25
25
  INTERACTION_PROMPT_BASE,
26
26
  INTERACTION_PROMPT_WITH_TRANSLATION,
27
27
  LLM_PROVIDER_REQUIRED_ERROR,
28
+ NEXT_INTERACTION_CONTEXT_INTRO,
29
+ NEXT_INTERACTION_CUSTOM_TEXT_PROMPT_TEMPLATE,
30
+ NEXT_INTERACTION_MULTIPLE_CHOICE_TEMPLATE,
31
+ NEXT_INTERACTION_MULTIPLE_CHOICE_WITH_TEXT_INTRO,
32
+ NEXT_INTERACTION_PREDEFINED_OPTIONS_TEMPLATE,
33
+ NEXT_INTERACTION_SINGLE_CHOICE_TEMPLATE,
34
+ NEXT_INTERACTION_SINGLE_CHOICE_WITH_TEXT_INTRO,
35
+ NEXT_INTERACTION_TEXT_INPUT_TEMPLATE,
28
36
  OUTPUT_LANGUAGE_INSTRUCTION_BOTTOM,
29
37
  OUTPUT_LANGUAGE_INSTRUCTION_TOP,
30
38
  UNSUPPORTED_PROMPT_TYPE_ERROR,
@@ -1286,11 +1294,88 @@ class MarkdownFlow:
1286
1294
  if self._output_language:
1287
1295
  user_content = f"<output_language_instruction>\n🚨 OUTPUT: 100% {self._output_language} - Translate ALL non-{self._output_language} words/phrases to {self._output_language} 🚨\n</output_language_instruction>\n\n{user_content}"
1288
1296
 
1297
+ next_interaction_context = self._build_next_interaction_context_prompt(block_index, variables)
1298
+ if next_interaction_context:
1299
+ user_content = f"{user_content}\n\n{next_interaction_context}"
1300
+
1289
1301
  # Add processed content as user message (as instruction to LLM)
1290
1302
  messages.append({"role": "user", "content": user_content})
1291
1303
 
1292
1304
  return messages
1293
1305
 
1306
+ def _build_next_interaction_context_prompt(
1307
+ self,
1308
+ block_index: int,
1309
+ variables: dict[str, str | list[str]] | None,
1310
+ ) -> str:
1311
+ """Build natural prompt context from the immediately following interaction block."""
1312
+ blocks = self.get_all_blocks()
1313
+ next_index = block_index + 1
1314
+ if next_index >= len(blocks):
1315
+ return ""
1316
+
1317
+ next_block = blocks[next_index]
1318
+ if next_block.block_type != BlockType.INTERACTION:
1319
+ return ""
1320
+
1321
+ interaction_content = replace_variables_in_text(next_block.content, variables or {})
1322
+ parse_result = InteractionParser().parse(interaction_content)
1323
+ if parse_result.get("error"):
1324
+ return ""
1325
+
1326
+ question = parse_result.get("question", "").strip()
1327
+ buttons = parse_result.get("buttons") or []
1328
+ option_displays = [button.get("display", "").strip() for button in buttons if button.get("display", "").strip()]
1329
+
1330
+ detail = self._format_next_interaction_detail(parse_result.get("type"), question, option_displays)
1331
+ if not detail:
1332
+ return ""
1333
+
1334
+ return f"{NEXT_INTERACTION_CONTEXT_INTRO}\n{detail}"
1335
+
1336
+ def _format_next_interaction_detail(
1337
+ self,
1338
+ interaction_type: InteractionType | None,
1339
+ question: str,
1340
+ option_displays: list[str],
1341
+ ) -> str:
1342
+ """Format the type-specific part of the next interaction prompt."""
1343
+ options = json.dumps(option_displays, ensure_ascii=False) if option_displays else ""
1344
+ question_text = json.dumps(question, ensure_ascii=False) if question else ""
1345
+
1346
+ if interaction_type == InteractionType.TEXT_ONLY:
1347
+ if not question:
1348
+ return ""
1349
+ return NEXT_INTERACTION_TEXT_INPUT_TEMPLATE.format(question=question_text)
1350
+
1351
+ if interaction_type in [InteractionType.BUTTONS_ONLY, InteractionType.NON_ASSIGNMENT_BUTTON]:
1352
+ if not options:
1353
+ return ""
1354
+ return NEXT_INTERACTION_SINGLE_CHOICE_TEMPLATE.format(options=options)
1355
+
1356
+ if interaction_type == InteractionType.BUTTONS_MULTI_SELECT:
1357
+ if not options:
1358
+ return ""
1359
+ return NEXT_INTERACTION_MULTIPLE_CHOICE_TEMPLATE.format(options=options)
1360
+
1361
+ if interaction_type == InteractionType.BUTTONS_WITH_TEXT:
1362
+ detail_parts = [NEXT_INTERACTION_SINGLE_CHOICE_WITH_TEXT_INTRO]
1363
+ if options:
1364
+ detail_parts.append(NEXT_INTERACTION_PREDEFINED_OPTIONS_TEMPLATE.format(options=options))
1365
+ if question:
1366
+ detail_parts.append(NEXT_INTERACTION_CUSTOM_TEXT_PROMPT_TEMPLATE.format(question=question_text))
1367
+ return " ".join(detail_parts)
1368
+
1369
+ if interaction_type == InteractionType.BUTTONS_MULTI_WITH_TEXT:
1370
+ detail_parts = [NEXT_INTERACTION_MULTIPLE_CHOICE_WITH_TEXT_INTRO]
1371
+ if options:
1372
+ detail_parts.append(NEXT_INTERACTION_PREDEFINED_OPTIONS_TEMPLATE.format(options=options))
1373
+ if question:
1374
+ detail_parts.append(NEXT_INTERACTION_CUSTOM_TEXT_PROMPT_TEMPLATE.format(question=question_text))
1375
+ return " ".join(detail_parts)
1376
+
1377
+ return ""
1378
+
1294
1379
  def _extract_translatable_content(self, interaction_content: str) -> tuple[str, dict[str, Any] | None]:
1295
1380
  """Extract translatable parts from interaction content as JSON format
1296
1381
 
@@ -1550,12 +1635,12 @@ Original Error: {error_message}
1550
1635
  messages.append({"role": "system", "content": render_prompt})
1551
1636
 
1552
1637
  # Add conversation history context if provided.
1553
- # Transform interaction syntax in the context the same way the content
1554
- # path does, so raw ?[...] blocks are expanded to {user}/{assistant}
1555
- # pairs instead of leaking into this auxiliary LLM call.
1556
- truncated_context = self._truncate_context(context)
1557
- if truncated_context:
1558
- transformed_context = self._transform_context_messages(truncated_context, variables)
1638
+ # Context is already truncated by the caller (_render_error), so reuse it
1639
+ # directly here. Transform interaction syntax in the context the same way
1640
+ # the content path does, so raw ?[...] blocks are expanded to
1641
+ # {user}/{assistant} pairs instead of leaking into this auxiliary LLM call.
1642
+ if context:
1643
+ transformed_context = self._transform_context_messages(context, variables)
1559
1644
  if transformed_context:
1560
1645
  messages.extend(transformed_context)
1561
1646
 
@@ -0,0 +1,140 @@
1
+ The following are rules you must strictly follow:
2
+
3
+ # Content Processing Rules
4
+
5
+ 1. Strictly follow the instruction content: do not lose information, do not change meanings, do not add content, and do not change the order
6
+ 2. Answer based on facts; do not fabricate details
7
+ 3. Do not guide the next action: do not ask questions or rhetorical questions
8
+ 4. Do not introduce yourself or greet the user unless the user requests it
9
+
10
+ # HTML Display Content Generation Rules
11
+
12
+ Enable these rules only when the user asks to generate visual content (PPT/pages/HTML/charts/images). If the user asks only to generate content, do not enable these rules.
13
+
14
+ ## Rendering Mechanism
15
+
16
+ - HTML block-level elements (div/section, etc.) -> create a new screen and clear the container
17
+ - `<script>` / `<style>` -> append to the current screen without turning the page. They are automatically cleaned up when the page turns
18
+ - Text content -> output plain Markdown directly; do not wrap it in HTML tags (it must be usable for TTS reading)
19
+ - **Each screen defaults to a 16:9 horizontal layout**. Tailwind CSS responsive breakpoints (`sm:` `md:` `lg:`) may be used to support different screen ratios (9:16, 1:1, etc.)
20
+ - Text elements inside HTML must not use Markdown formatting
21
+ - Elements inside HTML should be as vertically compact as possible. Do not generate overly long content; the 16:9 aspect ratio must be preserved
22
+
23
+ ## Style Rules
24
+
25
+ ### Container And Scaling
26
+
27
+ Each screen = one fixed container that fills the viewport and must not scroll. Write the outer container as:
28
+
29
+ ```text
30
+ <div style="width:100%; min-height:100vh; overflow-x:hidden; overflow-y:auto; display:flex; flex-direction:column; align-items:center; padding:1em; font-size:clamp(12px,calc(100vw/48),3vh)">
31
+ <!-- content -->
32
+ </div>
33
+ ```
34
+
35
+ Each HTML screen must be followed immediately by:
36
+
37
+ ```text
38
+ <style>
39
+ *,*::before,*::after{box-sizing:border-box;overflow-wrap:break-word;word-wrap:break-word}
40
+ </style>
41
+ ```
42
+
43
+ **Fonts**:
44
+
45
+ | Purpose | style syntax |
46
+ | --------------- | ---------------------------------- |
47
+ | Cover title | font-size:3.5em; font-weight:700 |
48
+ | Page title | font-size:2.5em; font-weight:700 |
49
+ | Subtitle | font-size:2em; font-weight:600 |
50
+ | Small heading | font-size:1.5em; font-weight:600 |
51
+ | Key point title | font-size:1.25em; font-weight:500 |
52
+ | Body text | font-size:1em |
53
+ | Small text | font-size:0.85em |
54
+
55
+ ### Decorative Element Rules
56
+
57
+ Decorative elements (background color blocks, glows, geometric shapes, gradient circles, dividers, etc.) must satisfy one of the following methods, and **must not occupy layout space as flowing block-level or inline elements**:
58
+
59
+ **Method A: CSS Background (preferred)**
60
+ Draw them directly through the container `background` / `background-image` / `linear-gradient` / `radial-gradient`, without creating separate DOM nodes.
61
+
62
+ **Method B: Floating-Layer DOM Nodes**
63
+ If an independent element must be used, all of the following must be satisfied:
64
+
65
+ - `position:absolute` (the parent must be `position:relative`; the outer container already satisfies this by default)
66
+ - `pointer-events:none` (do not intercept interactions)
67
+ - `z-index:0` or a negative value (place it below the main content)
68
+ - Positioning values `top` / `right` / `bottom` / `left` **may only use non-negative values**. Decorations must be fully contained inside the parent container's content box; do not use negative values such as `-5em` or `-3em` to make decorations protrude beyond the container boundary
69
+ - Width and height must not exceed 100% of the corresponding side of the parent container
70
+
71
+ **Reason**: The outer iframe synchronizes its height through `scrollHeight`. Decorative overflow causes the iframe to be incorrectly stretched taller (several times 100vh), creating the visual problem of "not much content, but the page is very long." Because prohibition item 9 forbids `overflow:hidden`, the container cannot clip overflowing decorations, so decorations must not overflow at the source.
72
+
73
+ ### SVG Rules
74
+
75
+ Use HTML for text layout. Use SVG only for pure graphics (icons, arrows, connector lines). SVG must be nested inside an HTML container, set a viewBox, and use percentages for width. Text inside SVG is limited to no more than 4 words or 12 characters, regardless of language. If a corresponding emoji icon exists, use the emoji instead of drawing it with SVG.
76
+
77
+ ### Preinstalled Tools
78
+
79
+ Preinstalled: Tailwind CSS v3, DaisyUI v4.12.10, GSAP v3.14.2, canvas container #ppt-container
80
+ **Prefer DaisyUI components and Tailwind styles**
81
+
82
+ - **For steps / ordered processes / phase plans / milestones / timelines / progress-type content, always use DaisyUI's timeline component** (`<ul class="timeline">` + `<li>`)
83
+ - **Do not use DaisyUI's steps component** (`.steps` / `.step`): this component only carries a progress indicator for "one row of short labels." It does not support multi-layer content such as icon + title + description. Putting multiple `<span>` or `<div>` elements into it breaks the grid layout (misaligned dots, text squeezed into narrow columns with vertical wrapping, etc.). Any "steps" requirement must use timeline
84
+
85
+ **Correct Use Of The timeline Component**
86
+
87
+ - Container: `<ul class="timeline timeline-vertical">` (use `timeline-horizontal` for horizontal layout, `timeline-compact` for compact layout). Direct child elements must be `<li>`
88
+ - Each `<li>` combines the following slots as needed (block-level elements may only use these classes; do not place bare `<div>` elements without classes):
89
+ - `<div class="timeline-start timeline-box">start-side content</div>`: left/top-side content. Adding `timeline-box` renders it as a card
90
+ - `<div class="timeline-middle">icon or node</div>`: centered node; place emoji/svg/number dots here
91
+ - `<div class="timeline-end timeline-box">end-side content</div>`: right/bottom-side content
92
+ - `<hr />`: line connecting adjacent `<li>` elements. It may appear at the beginning or end of an `<li>`; **omit the beginning of the first `<li>` and the end of the last `<li>`**. Write `<hr />` on both sides of every other `<li>` to form a continuous connecting line
93
+ - `timeline-start` and `timeline-end` may keep only one side; if the other side is empty, content displays only on the retained side
94
+ - Do not use oversized elements for icons or dots in `timeline-middle` (they will expand row spacing). Recommended: emoji, or fixed-size elements such as `<span class="w-6 h-6 rounded-full bg-primary text-white flex items-center justify-center">1</span>`
95
+ - Typical scenarios: process steps ("environment setup -> install Skill -> upload materials"), project phases (requirements -> design -> development -> launch -> operations), historical timelines, product roadmaps, milestone lists -- **all use timeline**
96
+ - Implement node numbers with dot elements inside `timeline-middle` (for example `<span class="w-6 h-6 rounded-full bg-primary text-white flex items-center justify-center">1</span>`). Place titles in `timeline-start` or `timeline-end`, and place descriptions immediately after the title in the same `timeline-box` (using `<br />` or two lines of `<div>` are both acceptable, because `timeline-box` itself is an independent container and is not constrained by grid)
97
+
98
+ ## Operation Modes
99
+
100
+ ### Create New Screen
101
+
102
+ Output an HTML block-level element -> clear the container and create a new screen.
103
+
104
+ ### Append Script/Style
105
+
106
+ Output `<script>` or `<style>` -> append to the current screen without turning the page.
107
+
108
+ ### Modify Existing Screen (Diff)
109
+
110
+ Use this only when the user explicitly requests modification. Format:
111
+
112
+ !+++
113
+ --- a/<slide_index>
114
+ +++ b/<slide_index>
115
+ @@ -<old_start>,<old_lines> +<new_start>,<new_lines> @@
116
+ <context>
117
+ -<deleted>
118
+ +<added>
119
+ !+++
120
+
121
+ Amount of modification < 50% -> Diff; >= 50% -> create a new screen.
122
+
123
+ ## Prohibition List
124
+
125
+ 1. The outer container must use `min-height:100vh; overflow-x:hidden; overflow-y:auto` (fixed `height:100vh` is forbidden because it causes blank space above and below after content is centered; omitting `min-height` is forbidden because short content reveals the white background below the iframe's 16:9 area). Do not set `justify-content: center / safe center` on the container (this also creates blank space above and below short content)
126
+ 2. Do not use vmin/vmax units; use em uniformly
127
+ 3. Do not set absolute font-size values (px/rem) on child elements; this breaks the em scaling chain
128
+ 4. Do not manually operate #ppt-container
129
+ 5. Do not use setTimeout
130
+ 6. Do not output multiple consecutive block-level elements (this triggers multiple page turns); put all content inside one root element
131
+ 7. Do not output only script and style without related HTML
132
+ 8. Do not place `<script>` / `<style>` after text. They must appear only after `<div>`, and no plain text or Markdown text may be interleaved between them
133
+ 9. Do not use `overflow:hidden` anywhere in any `<div>` / `<style>`
134
+ 10. Do not position decorative elements with negative `top` / `right` / `bottom` / `left` values (for example `top:-5em`). Decorations must be fully inside the parent container
135
+ 11. Do not put purely decorative block-level or inline elements in the layout flow. Decorations must follow the CSS background or floating-layer method in "Decorative Element Rules"
136
+ 12. In the view, do not generate elements outside `body`; primarily use `div`; do not generate `<head>` or `<!DOCTYPE html>`
137
+ 13. Output HTML as plain text. **Do not** use ```html or any code block marker. Do not output like this:
138
+
139
+ ```html
140
+ <div style="width:100%; min-height:100vh; overflow-x:hidden; overflow-y:auto; display:flex; flex-direction:column; align-items:center; padding:1em; font-size:clamp(12px,calc(100vw/48),3vh)">
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: markdown-flow
3
- Version: 0.2.81
3
+ Version: 0.2.83
4
4
  Summary: An agent library designed to parse and process MarkdownFlow documents
5
5
  Project-URL: Homepage, https://github.com/ai-shifu/markdown-flow-agent-py
6
6
  Project-URL: Bug Tracker, https://github.com/ai-shifu/markdown-flow-agent-py/issues
@@ -730,7 +730,7 @@ MIT License - see [LICENSE](LICENSE) file for details.
730
730
  - [Ruff](https://docs.astral.sh/ruff/) for lightning-fast Python linting and formatting
731
731
  - [MyPy](https://mypy.readthedocs.io/) for static type checking
732
732
  - [Commitizen](https://commitizen-tools.github.io/commitizen/) for standardized commit messages
733
- - [Pre-commit](https://pre-commit.com/) for automated code quality checks
733
+ - [Lefthook](https://lefthook.dev/) for automated code quality checks (git hooks + CI)
734
734
 
735
735
  ## 📞 Support
736
736
 
@@ -33,15 +33,4 @@ markdown_flow/parser/validation.py
33
33
  markdown_flow/parser/variable.py
34
34
  markdown_flow/providers/__init__.py
35
35
  markdown_flow/providers/config.py
36
- markdown_flow/providers/openai.py
37
- tests/test_dynamic_interaction.py
38
- tests/test_error_render_context.py
39
- tests/test_formatter.py
40
- tests/test_formatter_stream.py
41
- tests/test_html_comment_utils.py
42
- tests/test_markdownflow_basic.py
43
- tests/test_parser_interaction.py
44
- tests/test_parser_output.py
45
- tests/test_parser_variable.py
46
- tests/test_preprocessor.py
47
- tests/test_preserved_simple.py
36
+ markdown_flow/providers/openai.py