Skip to content

fix: preserve XML/custom tags in AgentFlow prompt editor#6123

Closed
octo-patch wants to merge 2 commits intoFlowiseAI:mainfrom
octo-patch:fix/issue-6047-preserve-xml-tags-in-editor
Closed

fix: preserve XML/custom tags in AgentFlow prompt editor#6123
octo-patch wants to merge 2 commits intoFlowiseAI:mainfrom
octo-patch:fix/issue-6047-preserve-xml-tags-in-editor

Conversation

@octo-patch
Copy link
Copy Markdown

Fixes #6047

Problem

When a prompt containing XML-like custom tags (e.g. <question>, <context>) is loaded into the AgentFlow V2 prompt editor, TipTap/ProseMirror strips those tags because they are not part of its HTML schema. The text content is preserved but the enclosing tags are lost. This breaks structured prompting techniques recommended by Claude, OpenAI, and other major LLM providers.

Root cause: setContent(value) in both VariableInput and RichTextEditor passes the stored string directly to ProseMirror's HTML parser. Unknown HTML elements are silently dropped.

Solution

Add two small helpers — isTiptapHtml and toTiptapContent — to both editor components:

  • isTiptapHtml(content) — detects whether the stored value is already TipTap-generated HTML (starts with a known block element like <p>, <h1>, <ul>, etc.).
  • toTiptapContent(value) — when the value is not TipTap HTML, escapes &, <, > as HTML entities and wraps each line in <p> so ProseMirror stores angle brackets as literal text.

This is transparent to the LLM: resolveVariables in buildAgentflow.ts already runs Turndown (with escape disabled) on HTML content before variable substitution, which decodes &lt;question&gt; back to <question>.

Changes

  • packages/agentflow/src/atoms/VariableInput.tsx
  • packages/agentflow/src/atoms/RichTextEditor.tsx

Testing

  1. Open AgentFlow V2, create an LLM node, open a prompt input.
  2. Enter <question>\n{{question}}\n</question>.
  3. Save and reopen — tags should be preserved.
  4. Run the flow — the LLM receives <question>…</question> intact.

Copy link
Copy Markdown
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request introduces the RichTextEditor and VariableInput components, leveraging TipTap for rich text editing and syntax highlighting. The review identifies several issues: missing typography properties in the theme tokens which will lead to runtime errors, a regex anchoring bug in HTML detection, and a potential infinite loop in the value synchronization logic. Suggestions were also provided to fix prop forwarding in styled components, correct a syntax error in the mention chip rendering, and extract shared utility functions to reduce code duplication.

Comment on lines +57 to +60
height: rows ? `${rows * tokens.typography.rowHeightRem}rem` : `${tokens.typography.singleLineHeightRem}rem`,
overflowY: rows ? 'auto' : 'hidden',
overflowX: rows ? 'auto' : 'hidden',
lineHeight: rows ? `${tokens.typography.rowHeightRem}em` : `${tokens.typography.singleLineLineHeightEm}em`,
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

critical

The tokens object imported from @/core/theme/tokens is missing the typography property, which will cause a runtime error when accessing tokens.typography.rowHeightRem, tokens.typography.singleLineHeightRem, or tokens.typography.singleLineLineHeightEm. Please ensure the design tokens are updated to include these values.

*/
const isTiptapHtml = (content: string): boolean => {
if (!content) return false
return /<(?:p|h[1-6]|ul|ol|pre|blockquote|hr)\b/i.test(content.trim())
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

The regex in isTiptapHtml should be anchored to the start of the string. Currently, it returns true if a known block tag appears anywhere in the content (e.g., in a plain-text sentence like "The

tag is used for paragraphs"), which would incorrectly prevent escaping of literal angle brackets. Additionally, consider extracting this and the other helper functions (plainTextToTiptapHtml, toTiptapContent) to a shared utility file to avoid duplication with VariableInput.tsx.

Suggested change
return /<(?:p|h[1-6]|ul|ol|pre|blockquote|hr)\b/i.test(content.trim())
return /^<(?:p|h[1-6]|ul|ol|pre|blockquote|hr)\b/i.test(content.trim())


/** Normalises a stored value for safe consumption by TipTap's `setContent`. */
const toTiptapContent = (value: string): string => {
if (!value) return ''
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

Returning an empty string for nullish or empty values causes an infinite loop in the useEffect sync logic. TipTap's getHTML() returns

for an empty document, so toTiptapContent("") !== editor.getHTML() will always be true, triggering setContent on every render. Removing this early return allows plainTextToTiptapHtml to correctly return

for empty strings, matching TipTap's internal state.

Suggested change
if (!value) return ''
if (value == null) return '<p></p>'
References
  1. Avoid calling setState in a useEffect cleanup function for components that do not unmount, as it can cause infinite re-render loops when props change. Reset state in the main useEffect body instead.

/* ── Styled wrapper ── */

const StyledEditorContent = styled(EditorContent, {
shouldForwardProp: (prop) => prop !== 'rows'
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

The disabled prop should be added to shouldForwardProp to prevent it from being passed down to the underlying EditorContent component, which may not expect it as a standard HTML attribute. This ensures consistency with the implementation in VariableInput.tsx.

Suggested change
shouldForwardProp: (prop) => prop !== 'rows'
shouldForwardProp: (prop) => !['rows', 'disabled'].includes(prop as string)

return [
'span',
mergeAttributes(this.HTMLAttributes ?? {}, options.HTMLAttributes),
`${options.suggestion?.char ?? '{{'}${node.attrs.label ?? node.attrs.id}}}`
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

There is an extra closing brace in the mention chip rendering. If the trigger character is {{, this template literal results in {{variableName}}} (three closing braces). It should likely be }} to match the standard double-brace syntax.

Suggested change
`${options.suggestion?.char ?? '{{'}${node.attrs.label ?? node.attrs.id}}}`
(options.suggestion?.char ?? '{{') + (node.attrs.label ?? node.attrs.id)

@HenryHengZJ HenryHengZJ closed this Apr 2, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[3.1.1] HTML/XML tags are getting stripped from prompt

2 participants