diff --git a/api/insurance/admin/ppt_admin_routes.py b/api/insurance/admin/ppt_admin_routes.py
index 57808dc..925dcc5 100644
--- a/api/insurance/admin/ppt_admin_routes.py
+++ b/api/insurance/admin/ppt_admin_routes.py
@@ -244,3 +244,14 @@ def get_settings():
def update_settings():
updated_by = getattr(request, "user_id", "")
return jsonify(ppt_admin_service.update_settings(request.get_json(force=True), updated_by))
+
+
+@ppt_admin_bp.route("/sync-models", methods=["POST"])
+@permission_required("config_manage")
+def sync_models():
+ data = request.get_json(force=True)
+ return jsonify(ppt_admin_service.sync_models(
+ provider=data.get("provider", ""),
+ api_key=data.get("api_key", ""),
+ base_url=data.get("base_url", ""),
+ ))
diff --git a/api/insurance/admin/ppt_admin_service.py b/api/insurance/admin/ppt_admin_service.py
index 1e0603e..1ac52e4 100644
--- a/api/insurance/admin/ppt_admin_service.py
+++ b/api/insurance/admin/ppt_admin_service.py
@@ -566,3 +566,58 @@ class PptAdminService:
db.session.add(SystemSetting(key=key, value=str(value), updated_by=updated_by))
db.session.commit()
return {"code": 0, "data": None}
+
+ def sync_models(self, provider: str, api_key: str, base_url: str = "") -> dict:
+ """从供应商 API 拉取可用模型列表。"""
+ if not api_key:
+ return {"code": 1001, "message": "请填写 API Key", "data": None}
+
+ try:
+ models = self._fetch_provider_models(provider, api_key, base_url)
+ except Exception as e:
+ logger.warning(f"同步模型失败: {e}")
+ return {"code": 5001, "message": f"同步失败: {e}", "data": None}
+
+ if not models:
+ return {"code": 0, "data": {"models": [], "message": "未获取到模型"}}
+
+ return {"code": 0, "data": {"models": models}}
+
+ def _fetch_provider_models(self, provider: str, api_key: str, base_url: str) -> list:
+ """根据供应商类型拉取模型列表。"""
+ import httpx
+
+ if provider == "deepseek":
+ url = "https://api.deepseek.com/v1/models"
+ headers = {"Authorization": f"Bearer {api_key}"}
+ resp = httpx.get(url, headers=headers, timeout=10)
+ resp.raise_for_status()
+ return [m["id"] for m in resp.json().get("data", [])]
+
+ elif provider == "gemini":
+ url = f"https://generativelanguage.googleapis.com/v1/models?key={api_key}"
+ resp = httpx.get(url, timeout=10)
+ resp.raise_for_status()
+ return [m["name"].split("/")[-1] for m in resp.json().get("models", [])]
+
+ elif provider == "minimax":
+ url = "https://api.minimax.chat/v1/models"
+ headers = {"Authorization": f"Bearer {api_key}"}
+ resp = httpx.get(url, headers=headers, timeout=10)
+ resp.raise_for_status()
+ data = resp.json()
+ models = data.get("models") or data.get("data", [])
+ return [m.get("id") or m.get("model", "") for m in models if m]
+
+ elif provider == "dify":
+ return [m["model"] for m in (self._fetch_dify_models() or [])]
+
+ else:
+ # 自定义供应商:OpenAI 兼容 /models 端点
+ if not base_url:
+ return []
+ url = f"{base_url.rstrip('/')}/models"
+ headers = {"Authorization": f"Bearer {api_key}"}
+ resp = httpx.get(url, headers=headers, timeout=10)
+ resp.raise_for_status()
+ return [m["id"] for m in resp.json().get("data", [])]
diff --git a/api/insurance/ppt/llm_client.py b/api/insurance/ppt/llm_client.py
index 369e895..794e6ea 100644
--- a/api/insurance/ppt/llm_client.py
+++ b/api/insurance/ppt/llm_client.py
@@ -35,7 +35,7 @@ PROVIDERS = {
"deepseek": LLMProviderConfig(
name="deepseek",
base_url="https://api.deepseek.com/v1",
- model="deepseek-chat",
+ model="deepseek-v4-pro",
max_retries=2,
rate_limit=0,
),
@@ -149,7 +149,16 @@ async def _call_provider(
"generationConfig": {"temperature": 0.3, "maxOutputTokens": 4096},
}
else:
- raise ValueError(f"Unknown provider: {config.name}")
+ # 自定义供应商:OpenAI 兼容格式
+ headers["Authorization"] = f"Bearer {api_key}"
+ base = config.base_url.rstrip("/")
+ url = f"{base}/chat/completions"
+ body = {
+ "model": config.model,
+ "messages": messages,
+ "temperature": 0.3,
+ "max_tokens": 4096,
+ }
async with httpx.AsyncClient(timeout=timeout_s) as client:
resp = await client.post(url, json=body, headers=headers)
@@ -159,20 +168,7 @@ async def _call_provider(
latency_ms = (time.monotonic() - start) * 1000
# 解析响应
- if config.name in ("deepseek", "minimax"):
- content = ""
- choices = data.get("choices", [])
- if choices:
- content = choices[0].get("message", {}).get("content", "")
- usage = data.get("usage")
- tokens = None
- if usage:
- tokens = {
- "input": usage.get("prompt_tokens", 0),
- "output": usage.get("completion_tokens", 0),
- }
- else:
- # Gemini
+ if config.name == "gemini":
content = ""
candidates = data.get("candidates", [])
if candidates:
@@ -186,6 +182,19 @@ async def _call_provider(
"input": usage_meta.get("promptTokenCount", 0),
"output": usage_meta.get("candidatesTokenCount", 0),
}
+ else:
+ # OpenAI 兼容格式(deepseek / minimax / 自定义供应商)
+ content = ""
+ choices = data.get("choices", [])
+ if choices:
+ content = choices[0].get("message", {}).get("content", "")
+ usage = data.get("usage")
+ tokens = None
+ if usage:
+ tokens = {
+ "input": usage.get("prompt_tokens", 0),
+ "output": usage.get("completion_tokens", 0),
+ }
return LLMResponse(
content=content,
diff --git a/frontend/src/pages/admin/PptSettingsAdmin.vue b/frontend/src/pages/admin/PptSettingsAdmin.vue
index e9ce080..5bfba76 100644
--- a/frontend/src/pages/admin/PptSettingsAdmin.vue
+++ b/frontend/src/pages/admin/PptSettingsAdmin.vue
@@ -20,34 +20,28 @@
-
-
从 Dify 获取
-
applyDifyModel('ppt', cmd)">
- 从 Dify 获取
-
-
-
- {{ m.label }} ({{ m.provider }}/{{ m.model }})
-
-
-
-
+
+
+
+
+
同步模型
-
-
+
+
-
- 通过 Dify 调用,无需配置 API Key
-
-
海报文案模型
@@ -63,34 +57,30 @@
-
-
从 Dify 获取
-
applyDifyModel('poster', cmd)">
- 从 Dify 获取
-
-
-
- {{ m.label }} ({{ m.provider }}/{{ m.model }})
-
-
-
-
+
+
+
+
+
+ 同步模型
+
-
-
+
+
-
- 通过 Dify 调用,无需配置 API Key
-
-
海报图片模型
@@ -133,13 +123,14 @@