feat(config): 添加模型助手功能以选择和管理模型

本次提交主要包含以下更改:

1. **后端更新**:
   - 在 `app/service/config/config_service.py` 中新增 `fetch_ui_models` 方法,用于获取可用于 UI 的模型列表,并处理相关的错误情况。
   - 在 `app/router/config_routes.py` 中新增 `/ui/models` 路由,提供模型列表的 API 接口,并添加身份验证逻辑。

2. **前端更新**:
   - 在 `app/static/js/config_editor.js` 中实现模型助手的功能,包括模型列表的加载、搜索和选择。
   - 在 `app/templates/config_editor.html` 中添加模型助手的模态框和相关的 UI 元素,允许用户从列表中选择模型。

这些更改旨在增强用户体验,使用户能够更方便地选择和管理模型,提高配置界面的交互性和功能性。
This commit is contained in:
snaily
2025-05-08 19:48:03 +08:00
parent 30bf666a57
commit f1f568afca
4 changed files with 405 additions and 44 deletions
+69 -25
View File
@@ -1,41 +1,49 @@
"""
配置服务模块
"""
import datetime
import json
from typing import Any, Dict, List
from dotenv import find_dotenv, load_dotenv
from fastapi import HTTPException
from sqlalchemy import insert, update
from app.config.config import Settings as ConfigSettings
from app.config.config import settings
from app.database.connection import database
from app.database.models import Settings
from app.config.config import Settings as ConfigSettings
from app.database.services import get_all_settings
from app.service.key.key_manager import get_key_manager_instance, reset_key_manager_instance
from app.log.logger import get_config_routes_logger
from app.service.key.key_manager import (
get_key_manager_instance,
reset_key_manager_instance,
)
from app.service.model.model_service import ModelService
logger = get_config_routes_logger()
class ConfigService:
"""配置服务类,用于管理应用程序配置"""
@staticmethod
async def get_config() -> Dict[str, Any]:
return settings.model_dump()
@staticmethod
async def update_config(config_data: Dict[str, Any]) -> Dict[str, Any]:
for key, value in config_data.items():
if hasattr(settings, key):
setattr(settings, key, value)
logger.debug(f"Updated setting in memory: {key}")
logger.debug(f"Updated setting in memory: {key}")
# 获取现有设置
existing_settings_raw: List[Dict[str, Any]] = await get_all_settings()
existing_settings_map: Dict[str, Dict[str, Any]] = {s['key']: s for s in existing_settings_raw}
existing_settings_map: Dict[str, Dict[str, Any]] = {
s["key"]: s for s in existing_settings_raw
}
existing_keys = set(existing_settings_map.keys())
settings_to_update: List[Dict[str, Any]] = []
@@ -47,7 +55,7 @@ class ConfigService:
# 处理不同类型的值
if isinstance(value, list):
db_value = json.dumps(value)
elif isinstance(value, dict): # 新增对 dict 类型的处理
elif isinstance(value, dict): # 新增对 dict 类型的处理
db_value = json.dumps(value)
elif isinstance(value, bool):
db_value = str(value).lower()
@@ -55,24 +63,26 @@ class ConfigService:
db_value = str(value)
# 仅当值发生变化时才更新
if key in existing_keys and existing_settings_map[key]['value'] == db_value:
continue
if key in existing_keys and existing_settings_map[key]["value"] == db_value:
continue
description = f"{key}配置项"
description = f"{key}配置项"
data = {
'key': key,
'value': db_value,
'description': description,
'updated_at': now
"key": key,
"value": db_value,
"description": description,
"updated_at": now,
}
if key in existing_keys:
# Preserve original description if not explicitly provided
data['description'] = existing_settings_map[key].get('description', description)
data["description"] = existing_settings_map[key].get(
"description", description
)
settings_to_update.append(data)
else:
data['created_at'] = now
data["created_at"] = now
settings_to_insert.append(data)
# 在事务中执行批量插入和更新
@@ -82,17 +92,19 @@ class ConfigService:
if settings_to_insert:
query_insert = insert(Settings).values(settings_to_insert)
await database.execute(query=query_insert)
logger.info(f"Bulk inserted {len(settings_to_insert)} settings.")
logger.info(
f"Bulk inserted {len(settings_to_insert)} settings."
)
if settings_to_update:
for setting_data in settings_to_update:
query_update = (
update(Settings)
.where(Settings.key == setting_data['key'])
.where(Settings.key == setting_data["key"])
.values(
value=setting_data['value'],
description=setting_data['description'],
updated_at=setting_data['updated_at']
value=setting_data["value"],
description=setting_data["description"],
updated_at=setting_data["updated_at"],
)
)
await database.execute(query=query_update)
@@ -112,7 +124,7 @@ class ConfigService:
# For now, we log the error and continue
return await ConfigService.get_config()
@staticmethod
async def reset_config() -> Dict[str, Any]:
"""
@@ -124,7 +136,9 @@ class ConfigService:
"""
# 1. 重新加载配置对象,它应该处理环境变量和 .env 的优先级
_reload_settings()
logger.info("Settings object reloaded, prioritizing system environment variables then .env file.")
logger.info(
"Settings object reloaded, prioritizing system environment variables then .env file."
)
# 2. 重置并重新初始化 KeyManager
try:
@@ -140,6 +154,36 @@ class ConfigService:
# 3. 返回更新后的配置
return await ConfigService.get_config()
@staticmethod
async def fetch_ui_models() -> List[Dict[str, Any]]:
"""获取用于UI显示的模型列表"""
try:
key_manager = await get_key_manager_instance()
model_service = ModelService()
api_key = await key_manager.get_first_valid_key()
if not api_key:
logger.error("No valid API keys available to fetch model list for UI.")
raise HTTPException(
status_code=500,
detail="No valid API keys available to fetch model list.",
)
models = await model_service.get_gemini_openai_models(api_key)
return models
except HTTPException as e:
# Re-raise HTTPExceptions directly if they are already specific
raise e
except Exception as e:
logger.error(
f"Failed to fetch models for UI in ConfigService: {e}", exc_info=True
)
# Raise a generic HTTPException for other errors
raise HTTPException(
status_code=500, detail=f"Failed to fetch models for UI: {str(e)}"
)
# 重新加载配置的函数
def _reload_settings():
"""重新加载环境变量并更新配置"""
@@ -147,4 +191,4 @@ def _reload_settings():
load_dotenv(find_dotenv(), override=True)
# 更新现有 settings 对象的属性,而不是新建实例
for key, value in ConfigSettings().model_dump().items():
setattr(settings, key, value)
setattr(settings, key, value)