feat: 多平台 Coding Plan 统一管理系统初始实现
- 支持 MiniMax/OpenAI/Google Gemini/智谱/Kimi 五个平台 - 插件化 Provider 架构,自动发现注册 - 多维度 QuotaRule 额度追踪(固定间隔/自然周期/API同步/手动) - OpenAI + Anthropic 兼容 API 代理,SSE 流式转发 - Model 路由表 + 额度耗尽自动 fallback - 多媒体任务队列(图片/语音/视频) - Vue3 + Tailwind 单文件 Web 仪表盘 - Docker 一键部署 Made-with: Cursor
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
"""MiniMax 适配器 -- 支持 chat / image / voice / video"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from typing import Any, AsyncGenerator
|
||||
|
||||
import httpx
|
||||
|
||||
from app.providers.base import BaseProvider, Capability, QuotaInfo
|
||||
|
||||
|
||||
class MiniMaxProvider(BaseProvider):
|
||||
name = "minimax"
|
||||
display_name = "MiniMax"
|
||||
capabilities = [Capability.CHAT, Capability.IMAGE, Capability.VOICE, Capability.VIDEO]
|
||||
|
||||
async def chat(
|
||||
self,
|
||||
messages: list[dict],
|
||||
model: str,
|
||||
plan: dict,
|
||||
stream: bool = True,
|
||||
**kwargs,
|
||||
) -> AsyncGenerator[str, None]:
|
||||
url = f"{self._base_url(plan)}/text/chatcompletion_v2"
|
||||
body: dict[str, Any] = {
|
||||
"model": model,
|
||||
"messages": messages,
|
||||
"stream": stream,
|
||||
**kwargs,
|
||||
}
|
||||
headers = self._build_headers(plan)
|
||||
|
||||
async with httpx.AsyncClient(timeout=120) as client:
|
||||
if stream:
|
||||
async with client.stream("POST", url, json=body, headers=headers) as resp:
|
||||
resp.raise_for_status()
|
||||
async for line in resp.aiter_lines():
|
||||
if line.startswith("data: "):
|
||||
yield line + "\n\n"
|
||||
else:
|
||||
resp = await client.post(url, json=body, headers=headers)
|
||||
resp.raise_for_status()
|
||||
yield json.dumps(resp.json())
|
||||
|
||||
async def generate_image(self, prompt: str, plan: dict, **kwargs) -> dict[str, Any]:
|
||||
url = f"{self._base_url(plan)}/image/generation"
|
||||
body = {"model": kwargs.get("model", "image-01"), "prompt": prompt, **kwargs}
|
||||
headers = self._build_headers(plan)
|
||||
async with httpx.AsyncClient(timeout=120) as client:
|
||||
resp = await client.post(url, json=body, headers=headers)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
async def generate_voice(self, text: str, plan: dict, **kwargs) -> bytes:
|
||||
url = f"{self._base_url(plan)}/tts/generation"
|
||||
body = {"text": text, "model": kwargs.get("model", "speech-02"), **kwargs}
|
||||
headers = self._build_headers(plan)
|
||||
async with httpx.AsyncClient(timeout=120) as client:
|
||||
resp = await client.post(url, json=body, headers=headers)
|
||||
resp.raise_for_status()
|
||||
return resp.content
|
||||
|
||||
async def generate_video(self, prompt: str, plan: dict, **kwargs) -> dict[str, Any]:
|
||||
url = f"{self._base_url(plan)}/video/generation"
|
||||
body = {"model": kwargs.get("model", "video-01"), "prompt": prompt, **kwargs}
|
||||
headers = self._build_headers(plan)
|
||||
async with httpx.AsyncClient(timeout=120) as client:
|
||||
resp = await client.post(url, json=body, headers=headers)
|
||||
resp.raise_for_status()
|
||||
return resp.json()
|
||||
|
||||
async def query_quota(self, plan: dict) -> QuotaInfo | None:
|
||||
"""MiniMax 余额查询"""
|
||||
try:
|
||||
url = f"{self._base_url(plan)}/account/balance"
|
||||
headers = self._build_headers(plan)
|
||||
async with httpx.AsyncClient(timeout=30) as client:
|
||||
resp = await client.get(url, headers=headers)
|
||||
resp.raise_for_status()
|
||||
data = resp.json()
|
||||
return QuotaInfo(
|
||||
quota_remaining=data.get("balance", 0),
|
||||
raw=data,
|
||||
)
|
||||
except Exception:
|
||||
return None
|
||||
Reference in New Issue
Block a user