first commit
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
"""Очистка ответа LLM перед озвучкой.
|
||||
|
||||
По ТЗ: ИИ должен говорить чистым «человеческим» текстом — без звёздочек,
|
||||
решёток, списков, ссылок и эмодзи. Первая линия обороны — системный промпт
|
||||
(запрещает разметку), эта функция — вторая (убирает то, что просочилось).
|
||||
"""
|
||||
from __future__ import annotations
|
||||
|
||||
import re
|
||||
|
||||
# Эмодзи и декоративные символы (TTS их либо молчит, либо читает мусором)
|
||||
_EMOJI = re.compile(
|
||||
"["
|
||||
"\U0001F000-\U0001FAFF" # эмодзи и пиктограммы
|
||||
"\U00002600-\U000027BF" # misc symbols
|
||||
"\U0001F1E6-\U0001F1FF" # флаги
|
||||
"\U00002B00-\U00002BFF" # стрелки/звёзды
|
||||
"\U0000FE00-\U0000FE0F" # variation selectors
|
||||
"\U00002190-\U000021FF" # стрелки
|
||||
"]+"
|
||||
)
|
||||
|
||||
_CODE_FENCE = re.compile(r"```[\s\S]*?(```|$)")
|
||||
# Служебный маркер многочастных ответов: [ЧАСТЬ 2 ИЗ 5] — вырезается до озвучки
|
||||
_PART_MARKER = re.compile(r"\s*\[ЧАСТЬ\s+\d+\s+ИЗ\s+\d+\]\s*", re.I)
|
||||
_IMAGE = re.compile(r"!\[([^\]]*)\]\([^)]*\)")
|
||||
_LINK = re.compile(r"\[([^\]]*)\]\([^)]*\)")
|
||||
_HEADER = re.compile(r"^[ \t]{0,3}#{1,6}[ \t]*", re.M)
|
||||
_BOLD = re.compile(r"(\*\*\*|\*\*|__)(?=\S)(.+?\S)\1")
|
||||
_ITALIC = re.compile(r"(?<![\w*\\])(\*|_)(?=\S)(.+?\S)\1(?![\w*])")
|
||||
_STRIKE = re.compile(r"~~(.+?)~~")
|
||||
_BULLET = re.compile(r"(?:^|\n)[ \t]*[-*+][ \t]+")
|
||||
_NUMBERED = re.compile(r"(?:^|\n)[ \t]*\d{1,3}[.)][ \t]+")
|
||||
_BLOCKQUOTE = re.compile(r"(?:^|\n)[ \t]*>[ \t]?")
|
||||
_HRULE = re.compile(r"(?:^|\n)[ \t]*([-_=*])[ \t]*\1[ \t]*\1[\1 \t=-]*(?:\n|$)")
|
||||
_INLINE_CODE = re.compile(r"`([^`]*)`")
|
||||
_MULTI_WS = re.compile(r"[ \t\f\v]+")
|
||||
_MULTI_NL = re.compile(r"\s*\n\s*")
|
||||
|
||||
|
||||
def clean_for_speech(text: str) -> str:
|
||||
"""Markdown/эмодзи → одна чистая строка, пригодная для TTS."""
|
||||
if not text:
|
||||
return ""
|
||||
|
||||
t = _CODE_FENCE.sub(" ", text)
|
||||
t = _PART_MARKER.sub(" ", t) # служебный маркер части — не для ушей
|
||||
t = _IMAGE.sub(r"\1", t)
|
||||
t = _LINK.sub(r"\1", t)
|
||||
t = _HEADER.sub("", t)
|
||||
t = _BOLD.sub(r"\2", t)
|
||||
t = _ITALIC.sub(r"\2", t)
|
||||
t = _STRIKE.sub(r"\1", t)
|
||||
t = _BULLET.sub(" ", t)
|
||||
t = _NUMBERED.sub(" ", t)
|
||||
t = _BLOCKQUOTE.sub(" ", t)
|
||||
t = _HRULE.sub(" ", t)
|
||||
t = _INLINE_CODE.sub(r"\1", t)
|
||||
t = _EMOJI.sub(" ", t)
|
||||
|
||||
# Переносы строк — в пробелы (для озвучки это одна речь)
|
||||
t = t.replace("\r", "")
|
||||
t = _MULTI_WS.sub(" ", t)
|
||||
t = _MULTI_NL.sub(" ", t)
|
||||
t = t.strip()
|
||||
|
||||
# Двойные знаки препинания после чистки («…!» и т.п.)
|
||||
t = re.sub(r"\s+([,.!?;:])", r"\1", t)
|
||||
t = re.sub(r"([,.!?;:])\1+", r"\1", t)
|
||||
return t
|
||||
Reference in New Issue
Block a user