mirror of https://github.com/Mai-with-u/MaiBot.git
Merge branch 'dev' of https://github.com/Mai-with-u/MaiBot into dev
commit
edfc8d053a
|
|
@ -75,7 +75,6 @@ max_embedding_workers = 12 # 嵌入/抽取并发线程数
|
|||
embedding_chunk_size = 16 # 每批嵌入的条数
|
||||
info_extraction_workers = 3 # 实体抽取同时执行线程数
|
||||
enable_ppr = true # 是否启用PPR,低配机器可关闭
|
||||
ppr_node_cap = 8000 # 图节点数超过该值时自动跳过PPR
|
||||
```
|
||||
|
||||
- `embedding_dimension`
|
||||
|
|
@ -101,8 +100,6 @@ ppr_node_cap = 8000 # 图节点数超过该值时自动跳过PPR
|
|||
- `true`:检索会结合向量+知识图,效果更好,但略慢;
|
||||
- `false`:只用向量检索,牺牲一定效果,性能更稳定。
|
||||
|
||||
- `ppr_node_cap`
|
||||
安全阈值:当图节点数超过阈值时自动跳过 PPR,以避免“大图”导致卡顿。
|
||||
|
||||
> 调参建议:
|
||||
> - 若导入/检索阶段机器明显“顶不住”(>=1MB的大文本,且分配配置<4C),优先调低:
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -196,8 +196,6 @@ max_embedding_workers = 3 # 嵌入/抽取并发线程数
|
|||
embedding_chunk_size = 4 # 每批嵌入的条数
|
||||
max_synonym_entities = 2000 # 同义边参与的实体数上限,超限则跳过
|
||||
enable_ppr = true # 是否启用PPR,低配机器可关闭
|
||||
ppr_node_cap = 8000 # 图节点数超过该值时跳过PPR
|
||||
webui_graph_default_limit = 200 # WebUI /graph 默认返回的最大节点数,避免大图负载
|
||||
|
||||
[keyword_reaction]
|
||||
keyword_rules = [
|
||||
|
|
@ -265,6 +263,25 @@ api_server_allowed_api_keys = [] # 新版API Server允许的API Key列表,为
|
|||
[telemetry] #发送统计信息,主要是看全球有多少只麦麦
|
||||
enable = true
|
||||
|
||||
[webui] # WebUI 独立服务器配置
|
||||
enabled = true # 是否启用WebUI
|
||||
mode = "production" # 模式: development(开发) 或 production(生产)
|
||||
host = "127.0.0.1" # WebUI 服务器监听地址
|
||||
# IPv4: 0.0.0.0 (所有IPv4接口) / 127.0.0.1 (仅本地)
|
||||
# IPv6: :: (所有接口,支持IPv4+IPv6双栈) / ::1 (仅本地IPv6)
|
||||
# 推荐使用 :: 实现双栈支持
|
||||
port = 8001 # WebUI 服务器端口
|
||||
|
||||
# 防爬虫配置
|
||||
anti_crawler_mode = "basic" # 防爬虫模式: false(禁用) / strict(严格) / loose(宽松) / basic(基础-只记录不阻止)
|
||||
allowed_ips = "127.0.0.1" # IP白名单(逗号分隔,支持精确IP、CIDR格式和通配符)
|
||||
# 示例: 127.0.0.1,192.168.1.0/24,172.17.0.0/16
|
||||
trusted_proxies = "" # 信任的代理IP列表(逗号分隔),只有来自这些IP的X-Forwarded-For才被信任
|
||||
# 示例: 127.0.0.1,192.168.1.1,172.17.0.1
|
||||
trust_xff = false # 是否启用X-Forwarded-For代理解析(默认false)
|
||||
# 启用后,仍要求直连IP在trusted_proxies中才会信任XFF头
|
||||
secure_cookie = false # 是否启用安全Cookie(仅通过HTTPS传输,默认false)
|
||||
|
||||
[experimental] #实验性功能
|
||||
# 为指定聊天添加额外的prompt配置
|
||||
# 格式: ["platform:id:type:prompt内容", ...]
|
||||
|
|
|
|||
|
|
@ -1,19 +1,3 @@
|
|||
# 麦麦主程序配置
|
||||
HOST=127.0.0.1
|
||||
PORT=8000
|
||||
|
||||
# WebUI 独立服务器配置
|
||||
WEBUI_ENABLED=true
|
||||
WEBUI_MODE=production # 模式: development(开发) 或 production(生产)
|
||||
WEBUI_HOST=0.0.0.0 # WebUI 服务器监听地址
|
||||
WEBUI_PORT=8001 # WebUI 服务器端口
|
||||
|
||||
# 防爬虫配置
|
||||
WEBUI_ANTI_CRAWLER_MODE=basic # 防爬虫模式: false(禁用) / strict(严格) / loose(宽松) / basic(基础-只记录不阻止)
|
||||
WEBUI_ALLOWED_IPS=127.0.0.1 # IP白名单(逗号分隔,支持精确IP、CIDR格式和通配符)
|
||||
# 示例: 127.0.0.1,192.168.1.0/24,172.17.0.0/16
|
||||
WEBUI_TRUSTED_PROXIES= # 信任的代理IP列表(逗号分隔),只有来自这些IP的X-Forwarded-For才被信任
|
||||
# 示例: 127.0.0.1,192.168.1.1,172.17.0.1
|
||||
WEBUI_TRUST_XFF=false # 是否启用X-Forwarded-For代理解析(默认false)
|
||||
# 启用后,仍要求直连IP在TRUSTED_PROXIES中才会信任XFF头
|
||||
WEBUI_SECURE_COOKIE=false # 是否启用安全Cookie(仅通过HTTPS传输,默认false)
|
||||
PORT=8000
|
||||
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
|
|
@ -11,7 +11,7 @@
|
|||
<link rel="icon" type="image/x-icon" href="/maimai.ico" />
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
||||
<title>MaiBot Dashboard</title>
|
||||
<script type="module" crossorigin src="/assets/index-C8KuzKjI.js"></script>
|
||||
<script type="module" crossorigin src="/assets/index-BNVFwwkr.js"></script>
|
||||
<link rel="modulepreload" crossorigin href="/assets/react-vendor-BmxF9s7Q.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/router-Bz250laD.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/utils-BXc2jIuz.js">
|
||||
|
|
@ -25,7 +25,7 @@
|
|||
<link rel="modulepreload" crossorigin href="/assets/reactflow-DLoXAt4c.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/uppy-CcUbCiwl.js">
|
||||
<link rel="modulepreload" crossorigin href="/assets/markdown-kUhwkcQP.js">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BwEmxTOV.css">
|
||||
<link rel="stylesheet" crossorigin href="/assets/index-BNRBabgA.css">
|
||||
</head>
|
||||
<body>
|
||||
<div id="root" class="notranslate"></div>
|
||||
|
|
|
|||
Loading…
Reference in New Issue