Merge pull request #203 from cogwheel0/enhance-tag-balancing-details-reasoning
feat(chat): Enhance tag balancing for details, think, and reasoning tags
This commit is contained in:
@@ -37,12 +37,18 @@ final _inlineDetailsPattern = RegExp(
|
|||||||
r'<details([^>]*)>((?:(?!</details>).)*)</details>',
|
r'<details([^>]*)>((?:(?!</details>).)*)</details>',
|
||||||
dotAll: true,
|
dotAll: true,
|
||||||
);
|
);
|
||||||
|
// Patterns for balancing <think> and <reasoning> tags (similar to <details>)
|
||||||
|
final _thinkOpenPattern = RegExp(r'<think>');
|
||||||
|
final _thinkClosePattern = RegExp(r'</think>');
|
||||||
|
final _reasoningOpenPattern = RegExp(r'<reasoning>');
|
||||||
|
final _reasoningClosePattern = RegExp(r'</reasoning>');
|
||||||
|
|
||||||
/// Sanitizes content to handle malformed HTML-like tags that might cause
|
/// Sanitizes content to handle malformed HTML-like tags that might cause
|
||||||
/// parsing issues, particularly with Pipe Functions (e.g., Gemini).
|
/// parsing issues, particularly with Pipe Functions (e.g., Gemini).
|
||||||
///
|
///
|
||||||
/// This function:
|
/// This function:
|
||||||
/// - Ensures all `<details>` tags are properly closed
|
/// - Ensures all `<details>`, `<think>`, and `<reasoning>` tags are properly
|
||||||
|
/// closed
|
||||||
/// - Converts inline `<details>...</details>` to multi-line format for proper
|
/// - Converts inline `<details>...</details>` to multi-line format for proper
|
||||||
/// block-level parsing
|
/// block-level parsing
|
||||||
/// - Removes orphan closing tags (those without matching opening tags)
|
/// - Removes orphan closing tags (those without matching opening tags)
|
||||||
@@ -51,15 +57,23 @@ final _inlineDetailsPattern = RegExp(
|
|||||||
String sanitizeContentForParsing(String content) {
|
String sanitizeContentForParsing(String content) {
|
||||||
if (content.isEmpty) return content;
|
if (content.isEmpty) return content;
|
||||||
|
|
||||||
// Quick check: skip if no details tags present (check for both opening and closing)
|
String result = content;
|
||||||
if (!content.contains('<details') && !content.contains('</details>')) {
|
|
||||||
|
// Check which tag types are present and need balancing
|
||||||
|
final hasDetails =
|
||||||
|
content.contains('<details') || content.contains('</details>');
|
||||||
|
final hasThink = content.contains('<think>') || content.contains('</think>');
|
||||||
|
final hasReasoning =
|
||||||
|
content.contains('<reasoning>') || content.contains('</reasoning>');
|
||||||
|
|
||||||
|
// Quick check: skip if no relevant tags present
|
||||||
|
if (!hasDetails && !hasThink && !hasReasoning) {
|
||||||
return content;
|
return content;
|
||||||
}
|
}
|
||||||
|
|
||||||
String result = content;
|
|
||||||
|
|
||||||
// Step 1: Convert inline <details>...</details> to multi-line format
|
// Step 1: Convert inline <details>...</details> to multi-line format
|
||||||
// This ensures the markdown block parser can properly detect them
|
// This ensures the markdown block parser can properly detect them
|
||||||
|
if (hasDetails) {
|
||||||
result = result.replaceAllMapped(_inlineDetailsPattern, (match) {
|
result = result.replaceAllMapped(_inlineDetailsPattern, (match) {
|
||||||
final attrs = match.group(1) ?? '';
|
final attrs = match.group(1) ?? '';
|
||||||
final inner = match.group(2) ?? '';
|
final inner = match.group(2) ?? '';
|
||||||
@@ -69,20 +83,49 @@ String sanitizeContentForParsing(String content) {
|
|||||||
}
|
}
|
||||||
return match.group(0)!;
|
return match.group(0)!;
|
||||||
});
|
});
|
||||||
|
}
|
||||||
|
|
||||||
// Step 2: Balance tags by removing orphan closing tags and adding
|
// Step 2: Balance tags by removing orphan closing tags and adding
|
||||||
// missing closing tags using depth tracking
|
// missing closing tags using depth tracking
|
||||||
result = _balanceDetailsTags(result);
|
if (hasDetails) {
|
||||||
|
result = _balanceTags(
|
||||||
|
result,
|
||||||
|
_detailsOpenPattern,
|
||||||
|
_detailsClosePattern,
|
||||||
|
'</details>',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (hasThink) {
|
||||||
|
result = _balanceTags(
|
||||||
|
result,
|
||||||
|
_thinkOpenPattern,
|
||||||
|
_thinkClosePattern,
|
||||||
|
'</think>',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
if (hasReasoning) {
|
||||||
|
result = _balanceTags(
|
||||||
|
result,
|
||||||
|
_reasoningOpenPattern,
|
||||||
|
_reasoningClosePattern,
|
||||||
|
'</reasoning>',
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Balances `<details>` tags by removing orphan closing tags and adding
|
/// Balances tags by removing orphan closing tags and adding missing closing
|
||||||
/// missing closing tags. Uses depth tracking to properly handle nested tags
|
/// tags. Uses depth tracking to properly handle nested tags and identify
|
||||||
/// and identify orphans anywhere in the content.
|
/// orphans anywhere in the content.
|
||||||
String _balanceDetailsTags(String content) {
|
String _balanceTags(
|
||||||
final openMatches = _detailsOpenPattern.allMatches(content).toList();
|
String content,
|
||||||
final closeMatches = _detailsClosePattern.allMatches(content).toList();
|
RegExp openPattern,
|
||||||
|
RegExp closePattern,
|
||||||
|
String closeTag,
|
||||||
|
) {
|
||||||
|
final openMatches = openPattern.allMatches(content).toList();
|
||||||
|
final closeMatches = closePattern.allMatches(content).toList();
|
||||||
|
|
||||||
if (openMatches.isEmpty && closeMatches.isEmpty) return content;
|
if (openMatches.isEmpty && closeMatches.isEmpty) return content;
|
||||||
|
|
||||||
@@ -121,7 +164,7 @@ String _balanceDetailsTags(String content) {
|
|||||||
|
|
||||||
// Add missing closing tags for unclosed opening tags
|
// Add missing closing tags for unclosed opening tags
|
||||||
if (depth > 0) {
|
if (depth > 0) {
|
||||||
result += '\n</details>' * depth;
|
result += '\n$closeTag' * depth;
|
||||||
}
|
}
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
Reference in New Issue
Block a user