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
|