From e48086b1c2ed98a76a50e0d91aa83809de9c857d Mon Sep 17 00:00:00 2001 From: pablodanswer Date: Thu, 17 Oct 2024 13:27:57 -0700 Subject: [PATCH] add slack markdown formatting (#2829) * add slack markdown formatting * nit * k --- .../danswer/danswerbot/slack/formatting.py | 66 +++++++++++++++++++ .../slack/handlers/handle_regular_answer.py | 4 +- backend/requirements/default.txt | 1 + 3 files changed, 70 insertions(+), 1 deletion(-) create mode 100644 backend/danswer/danswerbot/slack/formatting.py diff --git a/backend/danswer/danswerbot/slack/formatting.py b/backend/danswer/danswerbot/slack/formatting.py new file mode 100644 index 000000000..604c879df --- /dev/null +++ b/backend/danswer/danswerbot/slack/formatting.py @@ -0,0 +1,66 @@ +from mistune import Markdown # type: ignore +from mistune import Renderer # type: ignore + + +def format_slack_message(message: str | None) -> str: + renderer = Markdown(renderer=SlackRenderer()) + return renderer.render(message) + + +class SlackRenderer(Renderer): + SPECIALS: dict[str, str] = {"&": "&", "<": "<", ">": ">"} + + def escape_special(self, text: str) -> str: + for special, replacement in self.SPECIALS.items(): + text = text.replace(special, replacement) + return text + + def header(self, text: str, level: int, raw: str | None = None) -> str: + return f"*{text}*\n" + + def emphasis(self, text: str) -> str: + return f"_{text}_" + + def double_emphasis(self, text: str) -> str: + return f"*{text}*" + + def strikethrough(self, text: str) -> str: + return f"~{text}~" + + def list(self, body: str, ordered: bool = True) -> str: + lines = body.split("\n") + count = 0 + for i, line in enumerate(lines): + if line.startswith("li: "): + count += 1 + prefix = f"{count}. " if ordered else "• " + lines[i] = f"{prefix}{line[4:]}" + return "\n".join(lines) + + def list_item(self, text: str) -> str: + return f"li: {text}\n" + + def link(self, link: str, title: str | None, content: str | None) -> str: + escaped_link = self.escape_special(link) + if content: + return f"<{escaped_link}|{content}>" + if title: + return f"<{escaped_link}|{title}>" + return f"<{escaped_link}>" + + def image(self, src: str, title: str | None, text: str | None) -> str: + escaped_src = self.escape_special(src) + display_text = title or text + return f"<{escaped_src}|{display_text}>" if display_text else f"<{escaped_src}>" + + def codespan(self, text: str) -> str: + return f"`{text}`" + + def block_code(self, text: str, lang: str | None) -> str: + return f"```\n{text}\n```\n" + + def paragraph(self, text: str) -> str: + return f"{text}\n" + + def autolink(self, link: str, is_email: bool) -> str: + return link if is_email else self.link(link, None, None) diff --git a/backend/danswer/danswerbot/slack/handlers/handle_regular_answer.py b/backend/danswer/danswerbot/slack/handlers/handle_regular_answer.py index e864d92c7..c8aa5961e 100644 --- a/backend/danswer/danswerbot/slack/handlers/handle_regular_answer.py +++ b/backend/danswer/danswerbot/slack/handlers/handle_regular_answer.py @@ -27,6 +27,7 @@ from danswer.danswerbot.slack.blocks import build_follow_up_block from danswer.danswerbot.slack.blocks import build_qa_response_blocks from danswer.danswerbot.slack.blocks import build_sources_blocks from danswer.danswerbot.slack.blocks import get_restate_blocks +from danswer.danswerbot.slack.formatting import format_slack_message from danswer.danswerbot.slack.handlers.utils import send_team_member_message from danswer.danswerbot.slack.models import SlackMessageInfo from danswer.danswerbot.slack.utils import respond_in_thread @@ -412,10 +413,11 @@ def handle_regular_answer( # If called with the DanswerBot slash command, the question is lost so we have to reshow it restate_question_block = get_restate_blocks(messages[-1].message, is_bot_msg) + formatted_answer = format_slack_message(answer.answer) if answer.answer else None answer_blocks = build_qa_response_blocks( message_id=answer.chat_message_id, - answer=answer.answer, + answer=formatted_answer, quotes=answer.quotes.quotes if answer.quotes else None, source_filters=retrieval_info.applied_source_filters, time_cutoff=retrieval_info.applied_time_cutoff, diff --git a/backend/requirements/default.txt b/backend/requirements/default.txt index 25e85b06f..ef36de812 100644 --- a/backend/requirements/default.txt +++ b/backend/requirements/default.txt @@ -81,3 +81,4 @@ dropbox==11.36.2 boto3-stubs[s3]==1.34.133 ultimate_sitemap_parser==0.5 stripe==10.12.0 +mistune==0.8.4 \ No newline at end of file