新增 Intercom 渠道扩展,支持在 Yuxi 平台中集成 Intercom 客服渠道。 包含以下功能模块: - client: Intercom API 客户端封装 - config: 渠道配置管理 - gateway: SSE/WebSocket 网关接入 - webhook: Webhook 事件处理 - streaming: 流式消息处理 - format: 消息格式转换 - outbound: 外发消息管理 - handoff: 转人工会话切换 - pairing: 用户配对与绑定 - security: 安全签名校验 - dedupe: 消息去重 - monitor: 渠道状态监控 - status: 会话状态管理 - session_guard: 会话防护 - types: 类型定义
107 lines
3.2 KiB
Python
107 lines
3.2 KiB
Python
import html
|
|
import re
|
|
from html.parser import HTMLParser
|
|
|
|
|
|
class HTMLToTextParser(HTMLParser):
|
|
def __init__(self):
|
|
super().__init__()
|
|
self._parts: list[str] = []
|
|
self._skip_depth = 0
|
|
self._current_href: str | None = None
|
|
|
|
def handle_starttag(self, tag, attrs):
|
|
if tag in ("script", "style", "head", "meta", "link"):
|
|
self._skip_depth += 1
|
|
if tag == "a":
|
|
for name, value in attrs:
|
|
if name == "href":
|
|
self._current_href = value
|
|
break
|
|
|
|
def handle_endtag(self, tag):
|
|
if tag in ("script", "style", "head", "meta", "link") and self._skip_depth > 0:
|
|
self._skip_depth -= 1
|
|
if tag in ("p", "br", "li", "div", "tr", "h1", "h2", "h3", "h4", "h5", "h6"):
|
|
self._parts.append("\n")
|
|
if tag == "a" and self._current_href:
|
|
self._parts.append(f" ({self._current_href})")
|
|
self._current_href = None
|
|
|
|
def handle_data(self, data):
|
|
if self._skip_depth == 0:
|
|
self._parts.append(data)
|
|
|
|
def get_text(self) -> str:
|
|
return "".join(self._parts).strip()
|
|
|
|
|
|
def html_to_text(html_body: str) -> str:
|
|
parser = HTMLToTextParser()
|
|
parser.feed(html_body)
|
|
return parser.get_text()
|
|
|
|
|
|
def markdown_to_html(md_text: str) -> str:
|
|
text = html.escape(md_text)
|
|
|
|
text = re.sub(r"```(\w+)?\n?(.*?)```", r"<pre><code>\2</code></pre>", text, flags=re.DOTALL)
|
|
text = re.sub(r"`([^`]+)`", r"<code>\1</code>", text)
|
|
text = re.sub(r"\*\*(.+?)\*\*", r"<strong>\1</strong>", text)
|
|
text = re.sub(r"\*(.+?)\*", r"<em>\1</em>", text)
|
|
text = re.sub(r"~~(.+?)~~", r"<s>\1</s>", text)
|
|
text = re.sub(r"\[([^\]]+)\]\(([^)]+)\)", r'<a href="\2">\1</a>', text)
|
|
|
|
lines = text.split("\n")
|
|
result: list[str] = []
|
|
list_buffer: list[str] = []
|
|
in_list = False
|
|
|
|
for line in lines:
|
|
stripped = line.strip()
|
|
|
|
ul_match = re.match(r"^[\-\*]\s+(.+)$", stripped)
|
|
ol_match = re.match(r"^\d+\.\s+(.+)$", stripped)
|
|
|
|
if ul_match:
|
|
if not in_list:
|
|
in_list = True
|
|
list_buffer = []
|
|
list_buffer.append(f"<li>{ul_match.group(1)}</li>")
|
|
continue
|
|
|
|
if ol_match:
|
|
if not in_list:
|
|
in_list = True
|
|
list_buffer = []
|
|
list_buffer.append(f"<li>{ol_match.group(1)}</li>")
|
|
continue
|
|
|
|
if in_list:
|
|
in_list = False
|
|
result.append(f"<ul>{''.join(list_buffer)}</ul>")
|
|
list_buffer = []
|
|
|
|
heading_match = re.match(r"^#{1,6}\s+(.+)$", stripped)
|
|
if heading_match:
|
|
result.append(f"<p><strong>{heading_match.group(1)}</strong></p>")
|
|
continue
|
|
|
|
if not stripped:
|
|
result.append("<br>")
|
|
elif stripped.startswith("<") and not stripped.startswith("<br"):
|
|
result.append(stripped)
|
|
else:
|
|
result.append(f"<p>{stripped}</p>")
|
|
|
|
if in_list and list_buffer:
|
|
result.append(f"<ul>{''.join(list_buffer)}</ul>")
|
|
|
|
return "\n".join(result)
|
|
|
|
|
|
def get_agent_prompt_rules() -> list[str]:
|
|
from yuxi.channel.extensions.intercom.constants import INTERCOM_AGENT_PROMPT_RULES
|
|
|
|
return INTERCOM_AGENT_PROMPT_RULES
|