Merge branch 'dev' of https://github.com/Mai-with-u/MaiBot into dev
This commit is contained in:
@@ -42,7 +42,10 @@ def lpmm_start_up(): # sourcery skip: extract-duplicate-method
|
||||
logger.info("创建LLM客户端")
|
||||
|
||||
# 初始化Embedding库
|
||||
embed_manager = EmbeddingManager()
|
||||
embed_manager = EmbeddingManager(
|
||||
max_workers=global_config.lpmm_knowledge.max_embedding_workers,
|
||||
chunk_size=global_config.lpmm_knowledge.embedding_chunk_size,
|
||||
)
|
||||
logger.info("正在从文件加载Embedding库")
|
||||
try:
|
||||
embed_manager.load_from_file()
|
||||
|
||||
@@ -358,12 +358,9 @@ class KGManager:
|
||||
paragraph_search_result: ParagraphEmbedding的搜索结果(paragraph_hash, similarity)
|
||||
embed_manager: EmbeddingManager对象
|
||||
"""
|
||||
# 性能保护:关闭或超限时直接返回向量检索结果(仅基于节点规模与开关)
|
||||
if (
|
||||
not global_config.lpmm_knowledge.enable_ppr
|
||||
or len(self.graph.get_node_list()) > global_config.lpmm_knowledge.ppr_node_cap
|
||||
):
|
||||
logger.info("PPR 已禁用或超出阈值,使用纯向量检索结果")
|
||||
# 性能保护:关闭时直接返回向量检索结果
|
||||
if not global_config.lpmm_knowledge.enable_ppr:
|
||||
logger.info("PPR 已禁用,使用纯向量检索结果")
|
||||
return paragraph_search_result, None
|
||||
# 图中存在的节点总集
|
||||
existed_nodes = self.graph.get_node_list()
|
||||
|
||||
@@ -34,6 +34,7 @@ from src.config.official_configs import (
|
||||
MemoryConfig,
|
||||
DebugConfig,
|
||||
DreamConfig,
|
||||
WebUIConfig,
|
||||
)
|
||||
|
||||
from .api_ada_configs import (
|
||||
@@ -347,6 +348,7 @@ class Config(ConfigBase):
|
||||
response_post_process: ResponsePostProcessConfig
|
||||
response_splitter: ResponseSplitterConfig
|
||||
telemetry: TelemetryConfig
|
||||
webui: WebUIConfig
|
||||
experimental: ExperimentalConfig
|
||||
maim_message: MaimMessageConfig
|
||||
lpmm_knowledge: LPMMKnowledgeConfig
|
||||
|
||||
@@ -597,6 +597,38 @@ class TelemetryConfig(ConfigBase):
|
||||
"""是否启用遥测"""
|
||||
|
||||
|
||||
@dataclass
|
||||
class WebUIConfig(ConfigBase):
|
||||
"""WebUI配置类"""
|
||||
|
||||
enabled: bool = True
|
||||
"""是否启用WebUI"""
|
||||
|
||||
mode: Literal["development", "production"] = "production"
|
||||
"""运行模式:development(开发) 或 production(生产)"""
|
||||
|
||||
host: str = "0.0.0.0"
|
||||
"""WebUI服务器监听地址"""
|
||||
|
||||
port: int = 8001
|
||||
"""WebUI服务器端口"""
|
||||
|
||||
anti_crawler_mode: Literal["false", "strict", "loose", "basic"] = "basic"
|
||||
"""防爬虫模式:false(禁用) / strict(严格) / loose(宽松) / basic(基础-只记录不阻止)"""
|
||||
|
||||
allowed_ips: str = "127.0.0.1"
|
||||
"""IP白名单(逗号分隔,支持精确IP、CIDR格式和通配符)"""
|
||||
|
||||
trusted_proxies: str = ""
|
||||
"""信任的代理IP列表(逗号分隔),只有来自这些IP的X-Forwarded-For才被信任"""
|
||||
|
||||
trust_xff: bool = False
|
||||
"""是否启用X-Forwarded-For代理解析(默认false)"""
|
||||
|
||||
secure_cookie: bool = False
|
||||
"""是否启用安全Cookie(仅通过HTTPS传输,默认false)"""
|
||||
|
||||
|
||||
@dataclass
|
||||
class DebugConfig(ConfigBase):
|
||||
"""调试配置类"""
|
||||
@@ -722,6 +754,18 @@ class LPMMKnowledgeConfig(ConfigBase):
|
||||
embedding_dimension: int = 1024
|
||||
"""嵌入向量维度,应该与模型的输出维度一致"""
|
||||
|
||||
max_embedding_workers: int = 3
|
||||
"""嵌入/抽取并发线程数"""
|
||||
|
||||
embedding_chunk_size: int = 4
|
||||
"""每批嵌入的条数"""
|
||||
|
||||
max_synonym_entities: int = 2000
|
||||
"""同义边参与的实体数上限,超限则跳过"""
|
||||
|
||||
enable_ppr: bool = True
|
||||
"""是否启用PPR,低配机器可关闭"""
|
||||
|
||||
|
||||
@dataclass
|
||||
class DreamConfig(ConfigBase):
|
||||
|
||||
@@ -44,10 +44,9 @@ class MainSystem:
|
||||
|
||||
def _setup_webui_server(self):
|
||||
"""设置独立的 WebUI 服务器"""
|
||||
import os
|
||||
from src.config.config import global_config
|
||||
|
||||
webui_enabled = os.getenv("WEBUI_ENABLED", "false").lower() == "true"
|
||||
if not webui_enabled:
|
||||
if not global_config.webui.enabled:
|
||||
logger.info("WebUI 已禁用")
|
||||
return
|
||||
|
||||
|
||||
@@ -124,10 +124,8 @@ SCANNER_SPECIFIC_HEADERS = {
|
||||
# strict: 严格模式(更严格的检测,更低的频率限制)
|
||||
# loose: 宽松模式(较宽松的检测,较高的频率限制)
|
||||
# basic: 基础模式(只记录恶意访问,不阻止,不限制请求数,不跟踪IP)
|
||||
ANTI_CRAWLER_MODE = os.getenv("WEBUI_ANTI_CRAWLER_MODE", "basic").lower()
|
||||
|
||||
|
||||
# IP白名单配置(从环境变量读取,逗号分隔)
|
||||
# IP白名单配置(从配置文件读取,逗号分隔)
|
||||
# 支持格式:
|
||||
# - 精确IP:127.0.0.1, 192.168.1.100
|
||||
# - CIDR格式:192.168.1.0/24, 172.17.0.0/16 (适用于Docker网络)
|
||||
@@ -236,13 +234,23 @@ def _convert_wildcard_to_regex(wildcard_pattern: str) -> Optional[str]:
|
||||
return regex
|
||||
|
||||
|
||||
ALLOWED_IPS = _parse_allowed_ips(os.getenv("WEBUI_ALLOWED_IPS", ""))
|
||||
# 从配置读取防爬虫设置(延迟导入避免循环依赖)
|
||||
def _get_anti_crawler_config():
|
||||
"""获取防爬虫配置"""
|
||||
from src.config.config import global_config
|
||||
return {
|
||||
'mode': global_config.webui.anti_crawler_mode,
|
||||
'allowed_ips': _parse_allowed_ips(global_config.webui.allowed_ips),
|
||||
'trusted_proxies': _parse_allowed_ips(global_config.webui.trusted_proxies),
|
||||
'trust_xff': global_config.webui.trust_xff
|
||||
}
|
||||
|
||||
# 信任的代理IP配置(从环境变量读取,逗号分隔)
|
||||
# 只有在信任的代理IP下才使用X-Forwarded-For头
|
||||
# 默认关闭(空),不信任任何代理
|
||||
TRUSTED_PROXIES = _parse_allowed_ips(os.getenv("WEBUI_TRUSTED_PROXIES", ""))
|
||||
TRUST_XFF = os.getenv("WEBUI_TRUST_XFF", "false").lower() == "true"
|
||||
# 初始化配置(将在模块加载时执行)
|
||||
_config = _get_anti_crawler_config()
|
||||
ANTI_CRAWLER_MODE = _config['mode']
|
||||
ALLOWED_IPS = _config['allowed_ips']
|
||||
TRUSTED_PROXIES = _config['trusted_proxies']
|
||||
TRUST_XFF = _config['trust_xff']
|
||||
|
||||
|
||||
def _get_mode_config(mode: str) -> dict:
|
||||
|
||||
@@ -3,10 +3,10 @@ WebUI 认证模块
|
||||
提供统一的认证依赖,支持 Cookie 和 Header 两种方式
|
||||
"""
|
||||
|
||||
import os
|
||||
from typing import Optional
|
||||
from fastapi import HTTPException, Cookie, Header, Response, Request
|
||||
from src.common.logger import get_logger
|
||||
from src.config.config import global_config
|
||||
from .token_manager import get_token_manager
|
||||
|
||||
logger = get_logger("webui.auth")
|
||||
@@ -23,23 +23,18 @@ def _is_secure_environment() -> bool:
|
||||
Returns:
|
||||
bool: 如果应该使用 secure cookie 则返回 True
|
||||
"""
|
||||
# 检查环境变量
|
||||
secure_cookie_env = os.environ.get("WEBUI_SECURE_COOKIE", "")
|
||||
if secure_cookie_env.lower() in ("true", "1", "yes"):
|
||||
logger.info(f"WEBUI_SECURE_COOKIE 设置为 {secure_cookie_env},启用 secure cookie")
|
||||
# 从配置读取
|
||||
if global_config.webui.secure_cookie:
|
||||
logger.info("配置中启用了 secure_cookie")
|
||||
return True
|
||||
if secure_cookie_env.lower() in ("false", "0", "no"):
|
||||
logger.info(f"WEBUI_SECURE_COOKIE 设置为 {secure_cookie_env},禁用 secure cookie")
|
||||
return False
|
||||
|
||||
|
||||
# 检查是否是生产环境
|
||||
env = os.environ.get("WEBUI_MODE", "").lower()
|
||||
if env in ("production", "prod"):
|
||||
logger.info(f"WEBUI_MODE 设置为 {env},启用 secure cookie")
|
||||
if global_config.webui.mode == "production":
|
||||
logger.info("WebUI运行在生产模式,启用 secure cookie")
|
||||
return True
|
||||
|
||||
# 默认:开发环境不启用(因为通常是 HTTP)
|
||||
logger.debug(f"未设置特殊环境变量 (WEBUI_SECURE_COOKIE={secure_cookie_env}, WEBUI_MODE={env}),禁用 secure cookie")
|
||||
logger.debug("WebUI运行在开发模式,禁用 secure cookie")
|
||||
return False
|
||||
|
||||
|
||||
@@ -111,7 +106,7 @@ def set_auth_cookie(response: Response, token: str, request: Optional[Request] =
|
||||
logger.warning("=" * 80)
|
||||
logger.warning("检测到 HTTP 连接但环境配置要求 HTTPS (secure cookie)")
|
||||
logger.warning("已自动禁用 secure 标志以允许登录,但建议修改配置:")
|
||||
logger.warning("1. 在 .env 文件中设置: WEBUI_SECURE_COOKIE=false")
|
||||
logger.warning("1. 在配置文件中设置: webui.secure_cookie = false")
|
||||
logger.warning("2. 如果使用反向代理,请确保正确配置 X-Forwarded-Proto 头")
|
||||
logger.warning("=" * 80)
|
||||
is_secure = False
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
"""独立的 WebUI 服务器 - 运行在 0.0.0.0:8001"""
|
||||
|
||||
import os
|
||||
import asyncio
|
||||
import mimetypes
|
||||
from pathlib import Path
|
||||
@@ -130,9 +129,10 @@ class WebUIServer:
|
||||
"""配置防爬虫中间件"""
|
||||
try:
|
||||
from src.webui.anti_crawler import AntiCrawlerMiddleware
|
||||
from src.config.config import global_config
|
||||
|
||||
# 从环境变量读取防爬虫模式(false/strict/loose/basic)
|
||||
anti_crawler_mode = os.getenv("WEBUI_ANTI_CRAWLER_MODE", "basic").lower()
|
||||
# 从配置读取防爬虫模式
|
||||
anti_crawler_mode = global_config.webui.anti_crawler_mode
|
||||
|
||||
# 注意:中间件按注册顺序反向执行,所以先注册的中间件后执行
|
||||
# 我们需要在CORS之前注册,这样防爬虫检查会在CORS之前执行
|
||||
@@ -186,7 +186,7 @@ class WebUIServer:
|
||||
error_msg = f"❌ WebUI 服务器启动失败: 端口 {self.port} 已被占用"
|
||||
logger.error(error_msg)
|
||||
logger.error(f"💡 请检查是否有其他程序正在使用端口 {self.port}")
|
||||
logger.error("💡 可以通过环境变量 WEBUI_PORT 修改 WebUI 端口")
|
||||
logger.error("💡 可以在配置文件中修改 webui.port 来更改 WebUI 端口")
|
||||
logger.error(f"💡 Windows 用户可以运行: netstat -ano | findstr :{self.port}")
|
||||
logger.error(f"💡 Linux/Mac 用户可以运行: lsof -i :{self.port}")
|
||||
raise OSError(f"端口 {self.port} 已被占用,无法启动 WebUI 服务器")
|
||||
@@ -201,9 +201,21 @@ class WebUIServer:
|
||||
self._server = UvicornServer(config=config)
|
||||
|
||||
logger.info("🌐 WebUI 服务器启动中...")
|
||||
logger.info(f"🌐 访问地址: http://{self.host}:{self.port}")
|
||||
if self.host == "0.0.0.0":
|
||||
logger.info(f"本机访问请使用 http://localhost:{self.port}")
|
||||
|
||||
# 根据地址类型显示正确的访问地址
|
||||
if ':' in self.host:
|
||||
# IPv6 地址需要用方括号包裹
|
||||
logger.info(f"🌐 访问地址: http://[{self.host}]:{self.port}")
|
||||
if self.host == "::":
|
||||
logger.info(f"💡 IPv6 本机访问: http://[::1]:{self.port}")
|
||||
logger.info(f"💡 IPv4 本机访问: http://127.0.0.1:{self.port}")
|
||||
elif self.host == "::1":
|
||||
logger.info("💡 仅支持 IPv6 本地访问")
|
||||
else:
|
||||
# IPv4 地址
|
||||
logger.info(f"🌐 访问地址: http://{self.host}:{self.port}")
|
||||
if self.host == "0.0.0.0":
|
||||
logger.info(f"💡 本机访问: http://localhost:{self.port} 或 http://127.0.0.1:{self.port}")
|
||||
|
||||
try:
|
||||
await self._server.serve()
|
||||
@@ -212,7 +224,7 @@ class WebUIServer:
|
||||
if "address already in use" in str(e).lower() or e.errno in (98, 10048): # 98: Linux, 10048: Windows
|
||||
logger.error(f"❌ WebUI 服务器启动失败: 端口 {self.port} 已被占用")
|
||||
logger.error(f"💡 请检查是否有其他程序正在使用端口 {self.port}")
|
||||
logger.error("💡 可以通过环境变量 WEBUI_PORT 修改 WebUI 端口")
|
||||
logger.error("💡 可以在配置文件中修改 webui.port 来更改 WebUI 端口")
|
||||
else:
|
||||
logger.error(f"❌ WebUI 服务器启动失败 (网络错误): {e}")
|
||||
raise
|
||||
@@ -221,14 +233,24 @@ class WebUIServer:
|
||||
raise
|
||||
|
||||
def _check_port_available(self) -> bool:
|
||||
"""检查端口是否可用"""
|
||||
"""检查端口是否可用(支持 IPv4 和 IPv6)"""
|
||||
import socket
|
||||
|
||||
# 判断使用 IPv4 还是 IPv6
|
||||
if ':' in self.host:
|
||||
# IPv6 地址
|
||||
family = socket.AF_INET6
|
||||
test_host = self.host if self.host != "::" else "::1"
|
||||
else:
|
||||
# IPv4 地址
|
||||
family = socket.AF_INET
|
||||
test_host = self.host if self.host != "0.0.0.0" else "127.0.0.1"
|
||||
|
||||
try:
|
||||
with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
|
||||
with socket.socket(family, socket.SOCK_STREAM) as s:
|
||||
s.settimeout(1)
|
||||
# 尝试绑定端口
|
||||
s.bind((self.host if self.host != "0.0.0.0" else "127.0.0.1", self.port))
|
||||
s.bind((test_host, self.port))
|
||||
return True
|
||||
except OSError:
|
||||
return False
|
||||
@@ -257,8 +279,9 @@ def get_webui_server() -> WebUIServer:
|
||||
"""获取全局 WebUI 服务器实例"""
|
||||
global _webui_server
|
||||
if _webui_server is None:
|
||||
# 从环境变量读取配置
|
||||
host = os.getenv("WEBUI_HOST", "0.0.0.0")
|
||||
port = int(os.getenv("WEBUI_PORT", "8001"))
|
||||
# 从配置读取
|
||||
from src.config.config import global_config
|
||||
host = global_config.webui.host
|
||||
port = global_config.webui.port
|
||||
_webui_server = WebUIServer(host=host, port=port)
|
||||
return _webui_server
|
||||
|
||||
Reference in New Issue
Block a user