Spaces:
Paused
Paused
Upload 68 files
Browse files
app/config/__pycache__/config.cpython-311.pyc
ADDED
|
Binary file (29.8 kB). View file
|
|
|
app/config/config.py
CHANGED
|
@@ -10,6 +10,41 @@ from pydantic import ValidationError, ValidationInfo, field_validator
|
|
| 10 |
from pydantic_settings import BaseSettings
|
| 11 |
from sqlalchemy import insert, select, update
|
| 12 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 13 |
from app.core.constants import (
|
| 14 |
API_VERSION,
|
| 15 |
DEFAULT_CREATE_IMAGE_MODEL,
|
|
@@ -29,7 +64,7 @@ from app.log.logger import Logger
|
|
| 29 |
|
| 30 |
class Settings(BaseSettings):
|
| 31 |
# 数据库配置
|
| 32 |
-
DATABASE_TYPE: str = "
|
| 33 |
SQLITE_DATABASE: str = "default_db"
|
| 34 |
MYSQL_HOST: str = ""
|
| 35 |
MYSQL_PORT: int = 3306
|
|
@@ -51,8 +86,25 @@ class Settings(BaseSettings):
|
|
| 51 |
return v
|
| 52 |
|
| 53 |
# API相关配置
|
| 54 |
-
API_KEYS:
|
| 55 |
-
ALLOWED_TOKENS:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 56 |
BASE_URL: str = f"https://generativelanguage.googleapis.com/{API_VERSION}"
|
| 57 |
AUTH_TOKEN: str = ""
|
| 58 |
MAX_FAILURES: int = 3
|
|
@@ -72,6 +124,13 @@ class Settings(BaseSettings):
|
|
| 72 |
IMAGE_MODELS: List[str] = ["gemini-2.0-flash-exp"]
|
| 73 |
FILTERED_MODELS: List[str] = DEFAULT_FILTER_MODELS
|
| 74 |
TOOLS_CODE_EXECUTION_ENABLED: bool = False
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 75 |
SHOW_SEARCH_LINK: bool = True
|
| 76 |
SHOW_THINKING_PROCESS: bool = True
|
| 77 |
THINKING_MODELS: List[str] = []
|
|
@@ -90,7 +149,6 @@ class Settings(BaseSettings):
|
|
| 90 |
PICGO_API_KEY: str = ""
|
| 91 |
CLOUDFLARE_IMGBED_URL: str = ""
|
| 92 |
CLOUDFLARE_IMGBED_AUTH_CODE: str = ""
|
| 93 |
-
CLOUDFLARE_IMGBED_UPLOAD_FOLDER: str = ""
|
| 94 |
|
| 95 |
# 流式输出优化器配置
|
| 96 |
STREAM_OPTIMIZER_ENABLED: bool = False
|
|
@@ -125,12 +183,18 @@ class Settings(BaseSettings):
|
|
| 125 |
super().__init__(**kwargs)
|
| 126 |
# 设置默认AUTH_TOKEN(如果未提供)
|
| 127 |
if not self.AUTH_TOKEN and self.ALLOWED_TOKENS:
|
| 128 |
-
|
|
|
|
|
|
|
| 129 |
|
| 130 |
|
| 131 |
# 创建全局配置实例
|
| 132 |
settings = Settings()
|
| 133 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 134 |
|
| 135 |
def _parse_db_value(key: str, db_value: str, target_type: Type) -> Any:
|
| 136 |
"""尝试将数据库字符串值解析为目标 Python 类型"""
|
|
@@ -477,4 +541,4 @@ async def sync_initial_settings():
|
|
| 477 |
except Exception as e:
|
| 478 |
logger.error(f"Error disconnecting database after initial sync: {e}")
|
| 479 |
|
| 480 |
-
logger.info("Initial settings synchronization finished.")
|
|
|
|
| 10 |
from pydantic_settings import BaseSettings
|
| 11 |
from sqlalchemy import insert, select, update
|
| 12 |
|
| 13 |
+
|
| 14 |
+
def parse_comma_separated_string(v: Any) -> List[str]:
|
| 15 |
+
"""解析逗号分隔的字符串为字符串列表"""
|
| 16 |
+
# Handle None or empty values
|
| 17 |
+
if v is None or v == "":
|
| 18 |
+
return []
|
| 19 |
+
|
| 20 |
+
if isinstance(v, list):
|
| 21 |
+
return [str(item).strip() for item in v if str(item).strip()]
|
| 22 |
+
|
| 23 |
+
if isinstance(v, str):
|
| 24 |
+
# Handle empty string or whitespace-only string
|
| 25 |
+
if not v.strip():
|
| 26 |
+
return []
|
| 27 |
+
|
| 28 |
+
try:
|
| 29 |
+
# Attempt to parse as JSON list first, in case it's provided as such
|
| 30 |
+
parsed = json.loads(v)
|
| 31 |
+
if isinstance(parsed, list):
|
| 32 |
+
return [str(item).strip() for item in parsed if str(item).strip()]
|
| 33 |
+
except json.JSONDecodeError:
|
| 34 |
+
pass # Not a JSON string, proceed to comma split
|
| 35 |
+
|
| 36 |
+
# Split by comma and filter out empty strings
|
| 37 |
+
return [token.strip() for token in v.split(',') if token.strip()]
|
| 38 |
+
|
| 39 |
+
# For any other type, try to convert to string and process
|
| 40 |
+
try:
|
| 41 |
+
str_val = str(v)
|
| 42 |
+
if not str_val or str_val.strip() == "":
|
| 43 |
+
return []
|
| 44 |
+
return [token.strip() for token in str_val.split(',') if token.strip()]
|
| 45 |
+
except Exception:
|
| 46 |
+
return []
|
| 47 |
+
|
| 48 |
from app.core.constants import (
|
| 49 |
API_VERSION,
|
| 50 |
DEFAULT_CREATE_IMAGE_MODEL,
|
|
|
|
| 64 |
|
| 65 |
class Settings(BaseSettings):
|
| 66 |
# 数据库配置
|
| 67 |
+
DATABASE_TYPE: str = "sqlite" # sqlite 或 mysql
|
| 68 |
SQLITE_DATABASE: str = "default_db"
|
| 69 |
MYSQL_HOST: str = ""
|
| 70 |
MYSQL_PORT: int = 3306
|
|
|
|
| 86 |
return v
|
| 87 |
|
| 88 |
# API相关配置
|
| 89 |
+
API_KEYS: str = ""
|
| 90 |
+
ALLOWED_TOKENS: str = ""
|
| 91 |
+
|
| 92 |
+
@field_validator("API_KEYS")
|
| 93 |
+
@classmethod
|
| 94 |
+
def validate_api_keys(cls, v: str) -> str:
|
| 95 |
+
if not v or v.strip() == "":
|
| 96 |
+
raise ValueError("API_KEYS cannot be empty. Please provide at least one API key.")
|
| 97 |
+
return v
|
| 98 |
+
|
| 99 |
+
@property
|
| 100 |
+
def api_keys_list(self) -> List[str]:
|
| 101 |
+
"""将API_KEYS字符串转换为列表"""
|
| 102 |
+
return parse_comma_separated_string(self.API_KEYS)
|
| 103 |
+
|
| 104 |
+
@property
|
| 105 |
+
def allowed_tokens_list(self) -> List[str]:
|
| 106 |
+
"""将ALLOWED_TOKENS字符串转换为列表"""
|
| 107 |
+
return parse_comma_separated_string(self.ALLOWED_TOKENS)
|
| 108 |
BASE_URL: str = f"https://generativelanguage.googleapis.com/{API_VERSION}"
|
| 109 |
AUTH_TOKEN: str = ""
|
| 110 |
MAX_FAILURES: int = 3
|
|
|
|
| 124 |
IMAGE_MODELS: List[str] = ["gemini-2.0-flash-exp"]
|
| 125 |
FILTERED_MODELS: List[str] = DEFAULT_FILTER_MODELS
|
| 126 |
TOOLS_CODE_EXECUTION_ENABLED: bool = False
|
| 127 |
+
|
| 128 |
+
@field_validator("TOOLS_CODE_EXECUTION_ENABLED", mode="before")
|
| 129 |
+
@classmethod
|
| 130 |
+
def parse_boolean(cls, v: Any) -> bool:
|
| 131 |
+
if isinstance(v, str):
|
| 132 |
+
return v.lower() == "true"
|
| 133 |
+
return v
|
| 134 |
SHOW_SEARCH_LINK: bool = True
|
| 135 |
SHOW_THINKING_PROCESS: bool = True
|
| 136 |
THINKING_MODELS: List[str] = []
|
|
|
|
| 149 |
PICGO_API_KEY: str = ""
|
| 150 |
CLOUDFLARE_IMGBED_URL: str = ""
|
| 151 |
CLOUDFLARE_IMGBED_AUTH_CODE: str = ""
|
|
|
|
| 152 |
|
| 153 |
# 流式输出优化器配置
|
| 154 |
STREAM_OPTIMIZER_ENABLED: bool = False
|
|
|
|
| 183 |
super().__init__(**kwargs)
|
| 184 |
# 设置默认AUTH_TOKEN(如果未提供)
|
| 185 |
if not self.AUTH_TOKEN and self.ALLOWED_TOKENS:
|
| 186 |
+
tokens_list = self.allowed_tokens_list
|
| 187 |
+
if tokens_list:
|
| 188 |
+
self.AUTH_TOKEN = tokens_list[0]
|
| 189 |
|
| 190 |
|
| 191 |
# 创建全局配置实例
|
| 192 |
settings = Settings()
|
| 193 |
|
| 194 |
+
def get_settings() -> Settings:
|
| 195 |
+
"""获取配置实例"""
|
| 196 |
+
return settings
|
| 197 |
+
|
| 198 |
|
| 199 |
def _parse_db_value(key: str, db_value: str, target_type: Type) -> Any:
|
| 200 |
"""尝试将数据库字符串值解析为目标 Python 类型"""
|
|
|
|
| 541 |
except Exception as e:
|
| 542 |
logger.error(f"Error disconnecting database after initial sync: {e}")
|
| 543 |
|
| 544 |
+
logger.info("Initial settings synchronization finished.")
|
app/core/__pycache__/constants.cpython-311.pyc
ADDED
|
Binary file (2.19 kB). View file
|
|
|
app/core/application.py
CHANGED
|
@@ -43,7 +43,7 @@ async def _setup_database_and_config(app_settings):
|
|
| 43 |
logger.info("Database initialized successfully")
|
| 44 |
await connect_to_db()
|
| 45 |
await sync_initial_settings()
|
| 46 |
-
await get_key_manager_instance(app_settings.
|
| 47 |
logger.info("Database, config sync, and KeyManager initialized successfully")
|
| 48 |
|
| 49 |
|
|
|
|
| 43 |
logger.info("Database initialized successfully")
|
| 44 |
await connect_to_db()
|
| 45 |
await sync_initial_settings()
|
| 46 |
+
await get_key_manager_instance(app_settings.api_keys_list, app_settings.VERTEX_API_KEYS)
|
| 47 |
logger.info("Database, config sync, and KeyManager initialized successfully")
|
| 48 |
|
| 49 |
|
app/core/security.py
CHANGED
|
@@ -15,7 +15,7 @@ def verify_auth_token(token: str) -> bool:
|
|
| 15 |
class SecurityService:
|
| 16 |
|
| 17 |
async def verify_key(self, key: str):
|
| 18 |
-
if key not in settings.
|
| 19 |
logger.error("Invalid key")
|
| 20 |
raise HTTPException(status_code=401, detail="Invalid key")
|
| 21 |
return key
|
|
@@ -34,7 +34,7 @@ class SecurityService:
|
|
| 34 |
)
|
| 35 |
|
| 36 |
token = authorization.replace("Bearer ", "")
|
| 37 |
-
if token not in settings.
|
| 38 |
logger.error("Invalid token")
|
| 39 |
raise HTTPException(status_code=401, detail="Invalid token")
|
| 40 |
|
|
@@ -49,7 +49,7 @@ class SecurityService:
|
|
| 49 |
raise HTTPException(status_code=401, detail="Missing x-goog-api-key header")
|
| 50 |
|
| 51 |
if (
|
| 52 |
-
x_goog_api_key not in settings.
|
| 53 |
and x_goog_api_key != settings.AUTH_TOKEN
|
| 54 |
):
|
| 55 |
logger.error("Invalid x-goog-api-key")
|
|
@@ -75,7 +75,7 @@ class SecurityService:
|
|
| 75 |
) -> str:
|
| 76 |
"""验证URL中的key或请求头中的x-goog-api-key"""
|
| 77 |
# 如果URL中的key有效,直接返回
|
| 78 |
-
if key in settings.
|
| 79 |
return key
|
| 80 |
|
| 81 |
# 否则检查请求头中的x-goog-api-key
|
|
@@ -83,7 +83,7 @@ class SecurityService:
|
|
| 83 |
logger.error("Invalid key and missing x-goog-api-key header")
|
| 84 |
raise HTTPException(status_code=401, detail="Invalid key and missing x-goog-api-key header")
|
| 85 |
|
| 86 |
-
if x_goog_api_key not in settings.
|
| 87 |
logger.error("Invalid key and invalid x-goog-api-key")
|
| 88 |
raise HTTPException(status_code=401, detail="Invalid key and invalid x-goog-api-key")
|
| 89 |
|
|
|
|
| 15 |
class SecurityService:
|
| 16 |
|
| 17 |
async def verify_key(self, key: str):
|
| 18 |
+
if key not in settings.allowed_tokens_list and key != settings.AUTH_TOKEN:
|
| 19 |
logger.error("Invalid key")
|
| 20 |
raise HTTPException(status_code=401, detail="Invalid key")
|
| 21 |
return key
|
|
|
|
| 34 |
)
|
| 35 |
|
| 36 |
token = authorization.replace("Bearer ", "")
|
| 37 |
+
if token not in settings.allowed_tokens_list and token != settings.AUTH_TOKEN:
|
| 38 |
logger.error("Invalid token")
|
| 39 |
raise HTTPException(status_code=401, detail="Invalid token")
|
| 40 |
|
|
|
|
| 49 |
raise HTTPException(status_code=401, detail="Missing x-goog-api-key header")
|
| 50 |
|
| 51 |
if (
|
| 52 |
+
x_goog_api_key not in settings.allowed_tokens_list
|
| 53 |
and x_goog_api_key != settings.AUTH_TOKEN
|
| 54 |
):
|
| 55 |
logger.error("Invalid x-goog-api-key")
|
|
|
|
| 75 |
) -> str:
|
| 76 |
"""验证URL中的key或请求头中的x-goog-api-key"""
|
| 77 |
# 如果URL中的key有效,直接返回
|
| 78 |
+
if key in settings.allowed_tokens_list or key == settings.AUTH_TOKEN:
|
| 79 |
return key
|
| 80 |
|
| 81 |
# 否则检查请求头中的x-goog-api-key
|
|
|
|
| 83 |
logger.error("Invalid key and missing x-goog-api-key header")
|
| 84 |
raise HTTPException(status_code=401, detail="Invalid key and missing x-goog-api-key header")
|
| 85 |
|
| 86 |
+
if x_goog_api_key not in settings.allowed_tokens_list and x_goog_api_key != settings.AUTH_TOKEN:
|
| 87 |
logger.error("Invalid key and invalid x-goog-api-key")
|
| 88 |
raise HTTPException(status_code=401, detail="Invalid key and invalid x-goog-api-key")
|
| 89 |
|
app/log/__pycache__/logger.cpython-311.pyc
ADDED
|
Binary file (10.7 kB). View file
|
|
|
app/service/config/config_service.py
CHANGED
|
@@ -115,7 +115,7 @@ class ConfigService:
|
|
| 115 |
# 重置并重新初始化 KeyManager
|
| 116 |
try:
|
| 117 |
await reset_key_manager_instance()
|
| 118 |
-
await get_key_manager_instance(settings.
|
| 119 |
logger.info("KeyManager instance re-initialized with updated settings.")
|
| 120 |
except Exception as e:
|
| 121 |
logger.error(f"Failed to re-initialize KeyManager: {str(e)}")
|
|
@@ -125,17 +125,15 @@ class ConfigService:
|
|
| 125 |
@staticmethod
|
| 126 |
async def delete_key(key_to_delete: str) -> Dict[str, Any]:
|
| 127 |
"""删除单个API密钥"""
|
| 128 |
-
#
|
| 129 |
-
|
| 130 |
-
|
| 131 |
-
|
| 132 |
-
original_keys_count = len(settings.API_KEYS)
|
| 133 |
# 创建一个不包含待删除密钥的新列表
|
| 134 |
-
updated_api_keys = [k for k in
|
| 135 |
|
| 136 |
if len(updated_api_keys) < original_keys_count:
|
| 137 |
-
#
|
| 138 |
-
settings.API_KEYS = updated_api_keys
|
| 139 |
# 使用 update_config 持久化更改,它同时处理数据库和 KeyManager
|
| 140 |
await ConfigService.update_config({"API_KEYS": settings.API_KEYS})
|
| 141 |
logger.info(f"密钥 '{key_to_delete}' 已成功删除。")
|
|
@@ -148,13 +146,10 @@ class ConfigService:
|
|
| 148 |
@staticmethod
|
| 149 |
async def delete_selected_keys(keys_to_delete: List[str]) -> Dict[str, Any]:
|
| 150 |
"""批量删除选定的API密钥"""
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
deleted_count = 0
|
| 155 |
not_found_keys: List[str] = []
|
| 156 |
-
|
| 157 |
-
current_api_keys = list(settings.API_KEYS)
|
| 158 |
keys_actually_removed: List[str] = []
|
| 159 |
|
| 160 |
for key_to_del in keys_to_delete:
|
|
@@ -166,7 +161,8 @@ class ConfigService:
|
|
| 166 |
not_found_keys.append(key_to_del)
|
| 167 |
|
| 168 |
if deleted_count > 0:
|
| 169 |
-
|
|
|
|
| 170 |
await ConfigService.update_config({"API_KEYS": settings.API_KEYS})
|
| 171 |
logger.info(
|
| 172 |
f"成功删除 {deleted_count} 个密钥。密钥: {keys_actually_removed}"
|
|
@@ -213,7 +209,7 @@ class ConfigService:
|
|
| 213 |
try:
|
| 214 |
await reset_key_manager_instance()
|
| 215 |
# 确保使用更新后的 settings 中的 API_KEYS
|
| 216 |
-
await get_key_manager_instance(settings.
|
| 217 |
logger.info("KeyManager instance re-initialized with reloaded settings.")
|
| 218 |
except Exception as e:
|
| 219 |
logger.error(f"Failed to re-initialize KeyManager during reset: {str(e)}")
|
|
|
|
| 115 |
# 重置并重新初始化 KeyManager
|
| 116 |
try:
|
| 117 |
await reset_key_manager_instance()
|
| 118 |
+
await get_key_manager_instance(settings.api_keys_list, settings.VERTEX_API_KEYS)
|
| 119 |
logger.info("KeyManager instance re-initialized with updated settings.")
|
| 120 |
except Exception as e:
|
| 121 |
logger.error(f"Failed to re-initialize KeyManager: {str(e)}")
|
|
|
|
| 125 |
@staticmethod
|
| 126 |
async def delete_key(key_to_delete: str) -> Dict[str, Any]:
|
| 127 |
"""删除单个API密钥"""
|
| 128 |
+
# 获取当前API密钥列表
|
| 129 |
+
current_api_keys = settings.api_keys_list
|
| 130 |
+
original_keys_count = len(current_api_keys)
|
|
|
|
|
|
|
| 131 |
# 创建一个不包含待删除密钥的新列表
|
| 132 |
+
updated_api_keys = [k for k in current_api_keys if k != key_to_delete]
|
| 133 |
|
| 134 |
if len(updated_api_keys) < original_keys_count:
|
| 135 |
+
# 密钥已找到并从列表中移除,更新为逗号分隔的字符串
|
| 136 |
+
settings.API_KEYS = ",".join(updated_api_keys)
|
| 137 |
# 使用 update_config 持久化更改,它同时处理数据库和 KeyManager
|
| 138 |
await ConfigService.update_config({"API_KEYS": settings.API_KEYS})
|
| 139 |
logger.info(f"密钥 '{key_to_delete}' 已成功删除。")
|
|
|
|
| 146 |
@staticmethod
|
| 147 |
async def delete_selected_keys(keys_to_delete: List[str]) -> Dict[str, Any]:
|
| 148 |
"""批量删除选定的API密钥"""
|
| 149 |
+
# 获取当前API密钥列表
|
| 150 |
+
current_api_keys = settings.api_keys_list.copy()
|
|
|
|
| 151 |
deleted_count = 0
|
| 152 |
not_found_keys: List[str] = []
|
|
|
|
|
|
|
| 153 |
keys_actually_removed: List[str] = []
|
| 154 |
|
| 155 |
for key_to_del in keys_to_delete:
|
|
|
|
| 161 |
not_found_keys.append(key_to_del)
|
| 162 |
|
| 163 |
if deleted_count > 0:
|
| 164 |
+
# 更新为逗号分隔的字符串
|
| 165 |
+
settings.API_KEYS = ",".join(current_api_keys)
|
| 166 |
await ConfigService.update_config({"API_KEYS": settings.API_KEYS})
|
| 167 |
logger.info(
|
| 168 |
f"成功删除 {deleted_count} 个密钥。密钥: {keys_actually_removed}"
|
|
|
|
| 209 |
try:
|
| 210 |
await reset_key_manager_instance()
|
| 211 |
# 确保使用更新后的 settings 中的 API_KEYS
|
| 212 |
+
await get_key_manager_instance(settings.api_keys_list)
|
| 213 |
logger.info("KeyManager instance re-initialized with reloaded settings.")
|
| 214 |
except Exception as e:
|
| 215 |
logger.error(f"Failed to re-initialize KeyManager during reset: {str(e)}")
|