ForcePilot/backend/package/yuxi/channel/extensions/alexa/format.py
Kris 37493b6da6 feat(alexa): 新增Amazon Alexa渠道插件完整实现
新增了Alexa渠道的所有核心功能模块,包括配置管理、请求校验、会话管理、安全验证、去重、响应格式化、渐进式响应、主动推送、提醒功能、配对绑定以及Webhook端点,并完成了插件注册和配置项定义。
2026-05-21 10:38:31 +08:00

79 lines
2.8 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import html
import re
MAX_SSML_CHARS = 8000
VOICE_FRIENDLY_MAX = 1000
class MarkdownToSSML:
def __init__(self, max_chars: int = VOICE_FRIENDLY_MAX):
self._max_chars = max_chars
def convert(self, markdown_text: str) -> str:
if not markdown_text:
return "<speak></speak>"
text = self._strip_markdown(markdown_text)
text = html.escape(text, quote=False)
text = self._restore_placeholders(text)
text = self._truncate_if_needed(text)
return f"<speak>{text}</speak>"
def _strip_markdown(self, md: str) -> str:
self._placeholders = []
p = self._ph
md = re.sub(r'```[\s\S]*?```', p('此处有代码,请在手机 App 中查看。'), md)
md = re.sub(r'`([^`]+)`', r'\1', md)
md = re.sub(r'!\[.*?\]\(.*?\)', p('此处有图片。'), md)
md = re.sub(r'\*\*(.+?)\*\*', lambda m: p(f'<emphasis level="strong">{m.group(1)}</emphasis>'), md)
md = re.sub(r'\*([^*]+)\*', lambda m: p(f'<emphasis level="moderate">{m.group(1)}</emphasis>'), md)
md = re.sub(r'__([^_]+)__', r'\1', md)
md = re.sub(r'_([^_]+)_', r'\1', md)
md = re.sub(r'~~(.+?)~~', r'\1', md)
md = re.sub(r'\[(.+?)\]\(.+?\)', r'\1', md)
md = re.sub(r'^#{1,6}\s+', '', md, flags=re.MULTILINE)
md = re.sub(r'^[\-\*\+]\s+', '', md, flags=re.MULTILINE)
md = re.sub(r'^\d+\.\s+', '', md, flags=re.MULTILINE)
md = re.sub(r'^>\s+', '', md, flags=re.MULTILINE)
md = md.replace('---', p('<break time="500ms"/>'))
md = md.replace('***', p('<break time="500ms"/>'))
md = re.sub(r'\|.*\|', p('表格内容请在手机 App 中查看。'), md)
md = re.sub(r'https?://\S+', '', md)
return md.strip()
def _ph(self, s: str) -> str:
placeholder = f"\x00SSML{len(self._placeholders)}\x00"
self._placeholders.append(s)
return placeholder
def _restore_placeholders(self, text: str) -> str:
for i, val in enumerate(self._placeholders):
text = text.replace(f"\x00SSML{i}\x00", val)
return text
def _truncate_if_needed(self, text: str) -> str:
if len(text) <= self._max_chars:
return text
truncated = text[:self._max_chars]
delimiters = ['', '.', '\n', '', ';', '']
best_pos = -1
for delim in delimiters:
pos = truncated.rfind(delim)
if pos > best_pos:
best_pos = pos
if best_pos > 0:
truncated = truncated[:best_pos + 1]
truncated += '<break time="500ms"/>以上为部分回答,完整内容请在手机 App 中查看。'
return truncated
def ssml_safe(text: str) -> str:
if not text:
return "<speak></speak>"
text = html.escape(text, quote=False)
return f"<speak>{text}</speak>"