diff options
Diffstat (limited to 'llamachat/ui.py')
| -rw-r--r-- | llamachat/ui.py | 29 |
1 files changed, 28 insertions, 1 deletions
diff --git a/llamachat/ui.py b/llamachat/ui.py index fd2380c..f599bdd 100644 --- a/llamachat/ui.py +++ b/llamachat/ui.py @@ -1291,6 +1291,33 @@ _PRE_STYLE = ( ) +_FENCE_RE = re.compile(r"^\s{0,3}(`{3,}|~{3,})", re.MULTILINE) + + +def _close_fences(text: str) -> str: + """Close an unterminated code fence and neuter a lone inline backtick. + + A reply is streamed, so a fence is unclosed on nearly every frame; a + model can also emit an odd backtick by accident. Either way the rest of + the reply would reflow as code, which is how a whole message turns into + one grey block. Closing the fence here bounds the damage to the text + that was really meant as code. + """ + fences = _FENCE_RE.findall(text) + if len(fences) % 2: + # Match the opener's own length; a longer run is legal and only a + # fence at least as long as the opener actually closes it. + closer = fences[-1][0] * max(3, len(fences[-1])) + nl = "" if text.endswith("\n") else "\n" + return f"{text}{nl}{closer}" + if not fences and text.count("`") % 2: + # No fences at all, one dangling inline tick: drop it rather than + # let it open a run to the end of the message. + head, _, tail = text.rpartition("`") + return head + tail + return text + + def _markdown_to_fragment(text: str) -> str: """Render markdown to an HTML fragment safe to splice into the page. @@ -1300,7 +1327,7 @@ def _markdown_to_fragment(text: str) -> str: if not text: return "" doc = QTextDocument() - doc.setMarkdown(text, MARKDOWN_FLAGS) + doc.setMarkdown(_close_fences(text), MARKDOWN_FLAGS) html_text = doc.toHtml() start = html_text.find("<body") |
