feat: real-time reasoning speech, dynamic voice tags, and unified web UI bubbles
This commit is contained in:
+16
-1
@@ -29,10 +29,12 @@ _LIST_MARKER = re.compile(r"^[ \t]*[-*•]\s+", re.MULTILINE)
|
||||
# Identifiers read better as words: "sample_rate" -> "sample rate".
|
||||
_UNDERSCORE_WORD = re.compile(r"(?<=\w)_(?=\w)")
|
||||
_EXTRA_SPACE = re.compile(r"[ \t]{2,}")
|
||||
_CONTROL_TAGS = re.compile(r"\[(COMPLETE|NEEDS_DEEP|STATUS:[^\]]+)\]", re.IGNORECASE)
|
||||
_VOICE_TAG = re.compile(r"\[Voice:\s*([a-zA-Z0-9_\-]+)\]", re.IGNORECASE)
|
||||
|
||||
|
||||
class SpokenTextFilter(MarkdownTextFilter):
|
||||
"""Markdown filtering, plus the leftovers that matter when read aloud."""
|
||||
"""Markdown filtering, plus voice tag parsing and leftovers that matter when read aloud."""
|
||||
|
||||
def __init__(self, voice_manager=None, **kwargs):
|
||||
super().__init__(**kwargs)
|
||||
@@ -41,8 +43,21 @@ class SpokenTextFilter(MarkdownTextFilter):
|
||||
async def filter(self, text: str) -> str:
|
||||
if self._voice_manager:
|
||||
self._voice_manager.sync_voice()
|
||||
|
||||
# Intercept and set active voice on [Voice:VoiceName] tags
|
||||
match = _VOICE_TAG.search(text)
|
||||
if match:
|
||||
new_voice = match.group(1)
|
||||
text = _VOICE_TAG.sub("", text)
|
||||
if self._voice_manager:
|
||||
try:
|
||||
self._voice_manager.set_voice(new_voice)
|
||||
except Exception:
|
||||
pass
|
||||
|
||||
text = _TIMES.sub(" times ", text)
|
||||
text = await super().filter(text)
|
||||
text = _CONTROL_TAGS.sub("", text)
|
||||
text = _STRIKETHROUGH.sub(r"\1", text)
|
||||
text = _LIST_MARKER.sub("", text)
|
||||
text = _UNDERSCORE_WORD.sub(" ", text)
|
||||
|
||||
Reference in New Issue
Block a user