新增 Mattermost 渠道完整实现,包含适配器核心、消息处理、交互回调、命令支持、安全校验、多账号管理等功能,支持机器人消息发送、交互按钮、命令注册、投票功能以及配置动态修改等特性。
122 lines
3.8 KiB
Python
122 lines
3.8 KiB
Python
from __future__ import annotations
|
||
|
||
from typing import Any
|
||
|
||
from yuxi.utils.logging_config import logger
|
||
|
||
|
||
async def analyze_image(
|
||
adapter: Any,
|
||
file_id: str,
|
||
prompt: str = "Describe this image",
|
||
) -> str | None:
|
||
"""分析 Mattermost 图片 — 下载图片后通过 AI 视觉模型分析。
|
||
|
||
返回分析文本,失败返回 None。
|
||
"""
|
||
try:
|
||
image_data = await adapter.download_media(file_id)
|
||
except Exception as e:
|
||
logger.error(f"[Mattermost] Failed to download image for analysis: {e}")
|
||
return None
|
||
|
||
return await _call_vision_api(adapter, image_data, prompt)
|
||
|
||
|
||
async def analyze_attachment(
|
||
adapter: Any,
|
||
file_id: str,
|
||
file_name: str = "",
|
||
) -> str | None:
|
||
"""分析 Mattermost 附件 — 通用附件分析入口。"""
|
||
try:
|
||
attachment_data = await adapter.download_media(file_id)
|
||
except Exception as e:
|
||
logger.error(f"[Mattermost] Failed to download attachment for analysis: {e}")
|
||
return None
|
||
|
||
prompt = f"Analyze this file: {file_name}" if file_name else "Analyze this file"
|
||
return await _call_vision_api(adapter, attachment_data, prompt)
|
||
|
||
|
||
async def _call_vision_api(adapter: Any, data: bytes, prompt: str) -> str | None:
|
||
"""调用 AI 视觉 API 分析内容。"""
|
||
try:
|
||
import base64
|
||
|
||
base64_data = base64.b64encode(data).decode()
|
||
|
||
vision_config = (adapter.config or {}).get("vision", {})
|
||
provider = vision_config.get("provider", "openai")
|
||
model = vision_config.get("model", "gpt-4o")
|
||
|
||
if provider == "openai":
|
||
import openai
|
||
|
||
client = openai.AsyncOpenAI(api_key=vision_config.get("api_key", ""))
|
||
response = await client.chat.completions.create(
|
||
model=model,
|
||
messages=[
|
||
{
|
||
"role": "user",
|
||
"content": [
|
||
{"type": "text", "text": prompt},
|
||
{
|
||
"type": "image_url",
|
||
"image_url": {
|
||
"url": f"data:image/jpeg;base64,{base64_data}",
|
||
},
|
||
},
|
||
],
|
||
},
|
||
],
|
||
max_tokens=500,
|
||
)
|
||
return response.choices[0].message.content if response.choices else None
|
||
|
||
logger.warning(f"[Mattermost] Unsupported vision provider: {provider}")
|
||
return None
|
||
|
||
except ImportError:
|
||
logger.warning("[Mattermost] OpenAI library not installed for vision analysis")
|
||
return None
|
||
except Exception as e:
|
||
logger.error(f"[Mattermost] Vision API call failed: {e}")
|
||
return None
|
||
|
||
|
||
async def synthesize_speech(
|
||
adapter: Any,
|
||
text: str,
|
||
voice: str = "alloy",
|
||
) -> bytes | None:
|
||
"""TTS 语音合成 — 将文本转换为语音。
|
||
|
||
返回音频数据 bytes,失败返回 None。
|
||
"""
|
||
try:
|
||
tts_config = (adapter.config or {}).get("tts", {})
|
||
provider = tts_config.get("provider", "openai")
|
||
model = tts_config.get("model", "tts-1")
|
||
|
||
if provider == "openai":
|
||
import openai
|
||
|
||
client = openai.AsyncOpenAI(api_key=tts_config.get("api_key", ""))
|
||
response = await client.audio.speech.create(
|
||
model=model,
|
||
voice=voice,
|
||
input=text,
|
||
)
|
||
return response.content
|
||
|
||
logger.warning(f"[Mattermost] Unsupported TTS provider: {provider}")
|
||
return None
|
||
|
||
except ImportError:
|
||
logger.warning("[Mattermost] OpenAI library not installed for TTS")
|
||
return None
|
||
except Exception as e:
|
||
logger.error(f"[Mattermost] TTS synthesis failed: {e}")
|
||
return None
|