diff --git a/api/insurance/admin/ppt_admin_routes.py b/api/insurance/admin/ppt_admin_routes.py
index f298623..57808dc 100644
--- a/api/insurance/admin/ppt_admin_routes.py
+++ b/api/insurance/admin/ppt_admin_routes.py
@@ -227,6 +227,12 @@ def export_history():
# ---- 系统配置 ----
+@ppt_admin_bp.route("/available-models", methods=["GET"])
+@jwt_required
+def get_available_models():
+ return jsonify(ppt_admin_service.get_available_models())
+
+
@ppt_admin_bp.route("/settings", methods=["GET"])
@jwt_required
def get_settings():
diff --git a/api/insurance/admin/ppt_admin_service.py b/api/insurance/admin/ppt_admin_service.py
index 82c5416..81a19cd 100644
--- a/api/insurance/admin/ppt_admin_service.py
+++ b/api/insurance/admin/ppt_admin_service.py
@@ -476,6 +476,59 @@ class PptAdminService:
# ---- 系统配置 ----
+ # 内置常用模型列表(Dify 不可用时的降级方案)
+ _BUILTIN_MODELS = [
+ {"provider": "deepseek", "model": "deepseek-chat", "label": "DeepSeek Chat"},
+ {"provider": "deepseek", "model": "deepseek-reasoner", "label": "DeepSeek Reasoner"},
+ {"provider": "minimax", "model": "MiniMax-2.7-Flash", "label": "MiniMax 2.7 Flash"},
+ {"provider": "gemini", "model": "gemini-2.5-flash", "label": "Gemini 2.5 Flash"},
+ {"provider": "gemini", "model": "gemini-2.5-pro", "label": "Gemini 2.5 Pro"},
+ {"provider": "openai", "model": "gpt-4o", "label": "GPT-4o"},
+ {"provider": "openai", "model": "gpt-4o-mini", "label": "GPT-4o Mini"},
+ {"provider": "openai", "model": "gpt-image-1", "label": "GPT Image 1"},
+ ]
+
+ def get_available_models(self) -> dict:
+ """获取可用 LLM 模型列表,优先从 Dify 获取,失败时返回内置列表。"""
+ # 尝试从 Dify 获取
+ dify_models = self._fetch_dify_models()
+ if dify_models is not None:
+ return {"code": 0, "data": {"models": dify_models, "source": "dify"}}
+ return {"code": 0, "data": {"models": self._BUILTIN_MODELS, "source": "builtin"}}
+
+ def _fetch_dify_models(self) -> list | None:
+ """调用 Dify API 获取可用 LLM 模型列表,失败返回 None。"""
+ import requests
+ try:
+ # 从 SystemSetting 读取 Dify Workspace API Key
+ key_setting = SystemSetting.query.filter_by(key="dify_workspace_api_key").first()
+ api_key = key_setting.value if key_setting and key_setting.value else ""
+ if not api_key:
+ return None
+
+ base_url = os.getenv("DIFY_BASE_URL", "http://localhost:5001")
+ resp = requests.get(
+ f"{base_url}/v1/workspaces/current/models/model-types/llm",
+ headers={"Authorization": f"Bearer {api_key}"},
+ timeout=10,
+ )
+ if resp.status_code != 200:
+ logger.warning(f"Dify 模型查询失败: HTTP {resp.status_code}")
+ return None
+
+ data = resp.json()
+ models = []
+ for item in data.get("data", []):
+ provider = item.get("provider", "")
+ for model in item.get("models", []):
+ model_name = model.get("model", "")
+ label = model.get("label", {}).get("zh_Hans", "") or model.get("label", {}).get("en_US", "") or model_name
+ models.append({"provider": provider, "model": model_name, "label": label})
+ return models if models else None
+ except Exception as e:
+ logger.warning(f"从 Dify 获取模型列表失败: {e}")
+ return None
+
def get_settings(self) -> dict:
settings = SystemSetting.query.all()
return {
diff --git a/api/insurance/ppt/llm_client.py b/api/insurance/ppt/llm_client.py
index ff4fa2a..24015c7 100644
--- a/api/insurance/ppt/llm_client.py
+++ b/api/insurance/ppt/llm_client.py
@@ -204,8 +204,13 @@ class LLMClient:
self._configs: list[tuple[LLMProviderConfig, str]] = []
self._limiters: dict[str, RateLimiter] = {}
self._active_idx = 0
+ self._db_config_time: float = 0 # 上次从数据库加载配置的时间戳
+ self._db_config_ttl: float = 60 # 配置缓存有效期(秒)
- # 从环境变量加载
+ self._load_env_config()
+
+ def _load_env_config(self):
+ """从环境变量加载默认配置。"""
deepseek_key = os.environ.get("DEEPSEEK_API_KEY") or os.environ.get("OPENAI_API_KEY", "")
minimax_key = os.environ.get("MINIMAX_API_KEY", "")
gemini_key = os.environ.get("GEMINI_API_KEY", "")
@@ -223,6 +228,54 @@ class LLMClient:
if not self._configs:
logger.warning("[LLMClient] 未配置任何 API Key,使用 mock 模式")
+ def _try_load_db_config(self):
+ """尝试从数据库加载模型配置(带缓存,不阻塞)。"""
+ now = time.monotonic()
+ if now - self._db_config_time < self._db_config_ttl:
+ return
+ self._db_config_time = now
+ try:
+ from insurance.models.system_setting import SystemSetting
+ settings = {s.key: s.value for s in SystemSetting.query.filter(
+ SystemSetting.key.in_([
+ "ppt_llm_provider", "ppt_llm_model", "ppt_llm_api_key", "ppt_llm_base_url",
+ ])
+ ).all()}
+ provider = settings.get("ppt_llm_provider", "").strip()
+ api_key = settings.get("ppt_llm_api_key", "").strip()
+ if not provider or not api_key:
+ return
+
+ model = settings.get("ppt_llm_model", "").strip()
+ base_url = settings.get("ppt_llm_base_url", "").strip()
+
+ # 内置供应商:替换对应配置
+ if provider in PROVIDERS and not base_url:
+ cfg = PROVIDERS[provider]
+ if model:
+ cfg = LLMProviderConfig(
+ name=cfg.name, base_url=cfg.base_url, model=model,
+ max_retries=cfg.max_retries, rate_limit=cfg.rate_limit,
+ )
+ self._configs = [(cfg, api_key)]
+ self._limiters = {provider: RateLimiter(cfg.rate_limit)}
+ self._active_idx = 0
+ logger.info(f"[LLMClient] 使用数据库配置: {provider}/{cfg.model}")
+ return
+
+ # 自定义供应商
+ if not base_url:
+ return
+ cfg = LLMProviderConfig(
+ name=provider, base_url=base_url, model=model or "gpt-4o-mini",
+ )
+ self._configs = [(cfg, api_key)]
+ self._limiters = {provider: RateLimiter(0)}
+ self._active_idx = 0
+ logger.info(f"[LLMClient] 使用自定义模型: {provider}/{cfg.model}")
+ except Exception:
+ pass # 无 Flask 上下文或数据库不可用,使用环境变量配置
+
async def chat(self, prompt: str, system_prompt: str = "") -> LLMResponse:
"""简单聊天。"""
messages = []
@@ -271,6 +324,7 @@ class LLMClient:
async def _call(self, messages: list[dict], attempt: int = 0) -> LLMResponse:
"""多供应商自动切换调用。"""
+ self._try_load_db_config()
if not self._configs:
return LLMResponse(content="{}", provider="mock", latency_ms=0)
diff --git a/frontend/src/pages/admin/PptSettingsAdmin.vue b/frontend/src/pages/admin/PptSettingsAdmin.vue
index f87497c..4570a57 100644
--- a/frontend/src/pages/admin/PptSettingsAdmin.vue
+++ b/frontend/src/pages/admin/PptSettingsAdmin.vue
@@ -1,68 +1,233 @@
-
-
-
-
- 0 = 永久保留
-
-
-
- 0 = 永久保留
-
-
- 保存
-
-
-
+
+
+ PPT 解析模型
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
applyDifyModel('ppt', cmd)">
+ 从 Dify 获取
+
+
+
+ {{ m.label }} ({{ m.provider }}/{{ m.model }})
+
+
+
+
+
从 Dify 获取
+
+
+
+
+
+
+
+
+
+
+
+
+ 海报文案模型
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
applyDifyModel('poster', cmd)">
+ 从 Dify 获取
+
+
+
+ {{ m.label }} ({{ m.provider }}/{{ m.model }})
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ 海报图片模型
+
+
+
+
+
+
+
+
+
+
+ Dify 集成
+
+
+
+
+
+
+ 保存设置
+ 重置
+
+
diff --git a/frontend/src/utils/ppt-admin-api.ts b/frontend/src/utils/ppt-admin-api.ts
index c347742..8bf074b 100644
--- a/frontend/src/utils/ppt-admin-api.ts
+++ b/frontend/src/utils/ppt-admin-api.ts
@@ -102,4 +102,7 @@ export const pptAdminApi = {
updateSettings(data: Record) {
return api.put('/admin/ppt/settings', data)
},
+ getAvailableModels() {
+ return api.get('/admin/ppt/available-models')
+ },
}