remove:无用文件

This commit is contained in:
SengokuCola
2026-04-07 18:41:37 +08:00
parent 2233ee1af4
commit 297b1bf5e3
6 changed files with 15 additions and 726 deletions

View File

@@ -56,7 +56,7 @@ CONFIG_DIR: Path = PROJECT_ROOT / "config"
BOT_CONFIG_PATH: Path = (CONFIG_DIR / "bot_config.toml").resolve().absolute()
MODEL_CONFIG_PATH: Path = (CONFIG_DIR / "model_config.toml").resolve().absolute()
MMC_VERSION: str = "1.0.0"
CONFIG_VERSION: str = "8.5.0"
CONFIG_VERSION: str = "8.5.1"
MODEL_CONFIG_VERSION: str = "1.13.1"
logger = get_logger("config")
@@ -74,21 +74,18 @@ class Config(ConfigBase):
personality: PersonalityConfig = Field(default_factory=PersonalityConfig)
"""人格配置类"""
chat: ChatConfig = Field(default_factory=ChatConfig)
"""聊天配置类"""
visual: VisualConfig = Field(default_factory=VisualConfig)
"""视觉配置类"""
expression: ExpressionConfig = Field(default_factory=ExpressionConfig)
"""表达配置类"""
chat: ChatConfig = Field(default_factory=ChatConfig)
"""聊天配置类"""
memory: MemoryConfig = Field(default_factory=MemoryConfig)
"""记忆配置类"""
relationship: RelationshipConfig = Field(default_factory=RelationshipConfig)
"""关系配置类"""
message_receive: MessageReceiveConfig = Field(default_factory=MessageReceiveConfig)
"""消息接收配置类"""

View File

@@ -173,21 +173,6 @@ class VisualConfig(ConfigBase):
"""_wrap_识图提示词不建议修改"""
class RelationshipConfig(ConfigBase):
"""关系配置类"""
__ui_parent__ = "debug"
enable_relationship: bool = Field(
default=True,
json_schema_extra={
"x-widget": "switch",
"x-icon": "heart",
},
)
"""是否启用关系系统,关系系统被移除,此部分配置暂时无效"""
class TalkRulesItem(ConfigBase):
platform: str = ""
"""平台与ID一起留空表示全局"""
@@ -243,6 +228,16 @@ class ChatConfig(ConfigBase):
},
)
"""上下文长度"""
planner_interrupt_max_consecutive_count: int = Field(
default=2,
ge=0,
json_schema_extra={
"x-widget": "input",
"x-icon": "pause-circle",
},
)
"""Planner 连续被新消息打断的最大次数0 表示不启用打断"""
plan_reply_log_max_per_chat: int = Field(
default=1024,
@@ -1465,16 +1460,6 @@ class MaiSakaConfig(ConfigBase):
)
"""MaiSaka 使用的用户名称"""
planner_interrupt_max_consecutive_count: int = Field(
default=2,
ge=0,
json_schema_extra={
"x-widget": "input",
"x-icon": "pause-circle",
},
)
"""Planner 连续被新消息打断的最大次数0 表示不启用打断"""
tool_filter_task_name: str = Field(
default="utils",
json_schema_extra={

View File

@@ -94,7 +94,7 @@ class MaisakaHeartFlowChatting:
self._planner_interrupt_consecutive_count = 0
self._planner_interrupt_max_consecutive_count = max(
0,
int(global_config.maisaka.planner_interrupt_max_consecutive_count),
int(global_config.chat.planner_interrupt_max_consecutive_count),
)
expr_use, jargon_learn, expr_learn = ExpressionConfigUtils.get_expression_config_for_chat(session_id)

View File

@@ -1,117 +0,0 @@
"""
MaiSaka 工具处理器。
"""
from datetime import datetime
from typing import TYPE_CHECKING, Optional
import json as _json
from rich.panel import Panel
from src.cli.console import console
from src.cli.input_reader import InputReader
from src.llm_models.payload_content.tool_option import ToolCall
from .context_messages import LLMContextMessage, ToolResultMessage
if TYPE_CHECKING:
from src.mcp_module import MCPManager
class ToolHandlerContext:
"""工具处理器共享上下文。"""
def __init__(
self,
reader: InputReader,
user_input_times: list[datetime],
) -> None:
self.reader = reader
self.user_input_times = user_input_times
self.last_user_input_time: Optional[datetime] = None
async def handle_wait(tc: ToolCall, chat_history: list[LLMContextMessage], ctx: ToolHandlerContext) -> str:
"""处理 wait 工具。"""
seconds = (tc.args or {}).get("seconds", 30)
seconds = max(5, min(seconds, 300))
console.print(f"[accent]调用工具: wait({seconds})[/accent]")
tool_result = await _do_wait(seconds, ctx)
chat_history.append(
ToolResultMessage(
content=tool_result,
timestamp=datetime.now(),
tool_call_id=tc.call_id,
tool_name=tc.func_name,
)
)
return tool_result
async def _do_wait(seconds: int, ctx: ToolHandlerContext) -> str:
"""等待用户输入,支持超时。"""
console.print(f"[muted]等待用户输入中(超时: {seconds} 秒)...[/muted]")
console.print("[bold magenta]> [/bold magenta]", end="")
user_input = await ctx.reader.get_line(timeout=seconds)
if user_input is None:
console.print()
console.print("[muted]等待超时[/muted]")
return "等待超时,未收到用户输入。"
user_input = user_input.strip()
if not user_input:
return "用户提交了空输入。"
now = datetime.now()
ctx.last_user_input_time = now
ctx.user_input_times.append(now)
if user_input.lower() in ("/quit", "/exit", "/q"):
return "[[QUIT]] 用户请求退出。"
return f"已收到用户输入: {user_input}"
async def handle_mcp_tool(tc: ToolCall, chat_history: list[LLMContextMessage], mcp_manager: "MCPManager") -> None:
"""处理 MCP 工具调用。"""
args_str = _json.dumps(tc.args or {}, ensure_ascii=False)
args_preview = args_str if len(args_str) <= 120 else args_str[:120] + "..."
console.print(f"[accent]调用 MCP 工具: {tc.func_name}({args_preview})[/accent]")
with console.status(f"[info]正在执行 MCP 工具 {tc.func_name}...[/info]", spinner="dots"):
result = await mcp_manager.call_tool(tc.func_name, tc.args or {})
display_text = result if len(result) <= 800 else result[:800] + "\n...(已截断)"
console.print(
Panel(
display_text,
title=f"MCP 工具:{tc.func_name}",
border_style="bright_green",
padding=(0, 1),
)
)
chat_history.append(
ToolResultMessage(
content=result,
timestamp=datetime.now(),
tool_call_id=tc.call_id,
tool_name=tc.func_name,
)
)
async def handle_unknown_tool(tc: ToolCall, chat_history: list[LLMContextMessage]) -> None:
"""处理未知工具调用。"""
console.print(f"[accent]调用未知工具: {tc.func_name}({tc.args})[/accent]")
chat_history.append(
ToolResultMessage(
content=f"未知工具: {tc.func_name}",
timestamp=datetime.now(),
tool_call_id=tc.call_id,
tool_name=tc.func_name,
)
)

View File

@@ -1,303 +0,0 @@
"""
规划器监控API
提供规划器日志数据的查询接口
性能优化:
1. 聊天摘要只统计文件数量和最新时间戳,不读取文件内容
2. 日志列表使用文件名解析时间戳,只在需要时读取完整内容
3. 详情按需加载
"""
import json
from pathlib import Path
from typing import Dict, List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from src.webui.dependencies import require_auth
router = APIRouter(prefix="/api/planner", tags=["planner"], dependencies=[Depends(require_auth)])
# 规划器日志目录
PLAN_LOG_DIR = Path("logs/plan")
class ChatSummary(BaseModel):
"""聊天摘要 - 轻量级,不读取文件内容"""
chat_id: str
plan_count: int
latest_timestamp: float
latest_filename: str
class PlanLogSummary(BaseModel):
"""规划日志摘要"""
chat_id: str
timestamp: float
filename: str
action_count: int
action_types: List[str] # 动作类型列表
total_plan_ms: float
llm_duration_ms: float
reasoning_preview: str
class PlanLogDetail(BaseModel):
"""规划日志详情"""
type: str
chat_id: str
timestamp: float
prompt: str
reasoning: str
raw_output: str
actions: List[Dict]
timing: Dict
extra: Optional[Dict] = None
class PlannerOverview(BaseModel):
"""规划器总览 - 轻量级统计"""
total_chats: int
total_plans: int
chats: List[ChatSummary]
class PaginatedChatLogs(BaseModel):
"""分页的聊天日志列表"""
data: List[PlanLogSummary]
total: int
page: int
page_size: int
chat_id: str
def parse_timestamp_from_filename(filename: str) -> float:
"""从文件名解析时间戳: 1766497488220_af92bdb1.json -> 1766497488.220"""
try:
timestamp_str = filename.split("_")[0]
# 时间戳是毫秒级,需要转换为秒
return float(timestamp_str) / 1000
except (ValueError, IndexError):
return 0
@router.get("/overview", response_model=PlannerOverview)
async def get_planner_overview():
"""
获取规划器总览 - 轻量级接口
只统计文件数量,不读取文件内容
"""
if not PLAN_LOG_DIR.exists():
return PlannerOverview(total_chats=0, total_plans=0, chats=[])
chats = []
total_plans = 0
for chat_dir in PLAN_LOG_DIR.iterdir():
if not chat_dir.is_dir():
continue
# 只统计json文件数量
json_files = list(chat_dir.glob("*.json"))
plan_count = len(json_files)
total_plans += plan_count
if plan_count == 0:
continue
# 从文件名获取最新时间戳
latest_file = max(json_files, key=lambda f: parse_timestamp_from_filename(f.name))
latest_timestamp = parse_timestamp_from_filename(latest_file.name)
chats.append(
ChatSummary(
chat_id=chat_dir.name,
plan_count=plan_count,
latest_timestamp=latest_timestamp,
latest_filename=latest_file.name,
)
)
# 按最新时间戳排序
chats.sort(key=lambda x: x.latest_timestamp, reverse=True)
return PlannerOverview(total_chats=len(chats), total_plans=total_plans, chats=chats)
@router.get("/chat/{chat_id}/logs", response_model=PaginatedChatLogs)
async def get_chat_plan_logs(
chat_id: str,
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
search: Optional[str] = Query(None, description="搜索关键词,匹配提示词内容"),
):
"""
获取指定聊天的规划日志列表(分页)
需要读取文件内容获取摘要信息
支持搜索提示词内容
"""
chat_dir = PLAN_LOG_DIR / chat_id
if not chat_dir.exists():
return PaginatedChatLogs(data=[], total=0, page=page, page_size=page_size, chat_id=chat_id)
# 先获取所有文件并按时间戳排序
json_files = list(chat_dir.glob("*.json"))
json_files.sort(key=lambda f: parse_timestamp_from_filename(f.name), reverse=True)
# 如果有搜索关键词,需要过滤文件
if search:
search_lower = search.lower()
filtered_files = []
for log_file in json_files:
try:
with open(log_file, "r", encoding="utf-8") as f:
data = json.load(f)
prompt = data.get("prompt", "")
if search_lower in prompt.lower():
filtered_files.append(log_file)
except Exception:
continue
json_files = filtered_files
total = len(json_files)
# 分页 - 只读取当前页的文件
offset = (page - 1) * page_size
page_files = json_files[offset : offset + page_size]
logs = []
for log_file in page_files:
try:
with open(log_file, "r", encoding="utf-8") as f:
data = json.load(f)
reasoning = data.get("reasoning", "")
actions = data.get("actions", [])
action_types = [a.get("action_type", "") for a in actions if a.get("action_type")]
logs.append(
PlanLogSummary(
chat_id=data.get("chat_id", chat_id),
timestamp=data.get("timestamp", parse_timestamp_from_filename(log_file.name)),
filename=log_file.name,
action_count=len(actions),
action_types=action_types,
total_plan_ms=data.get("timing", {}).get("total_plan_ms", 0),
llm_duration_ms=data.get("timing", {}).get("llm_duration_ms", 0),
reasoning_preview=reasoning[:100] if reasoning else "",
)
)
except Exception:
# 文件读取失败时使用文件名信息
logs.append(
PlanLogSummary(
chat_id=chat_id,
timestamp=parse_timestamp_from_filename(log_file.name),
filename=log_file.name,
action_count=0,
action_types=[],
total_plan_ms=0,
llm_duration_ms=0,
reasoning_preview="[读取失败]",
)
)
return PaginatedChatLogs(data=logs, total=total, page=page, page_size=page_size, chat_id=chat_id)
@router.get("/log/{chat_id}/{filename}", response_model=PlanLogDetail)
async def get_log_detail(chat_id: str, filename: str):
"""获取规划日志详情 - 按需加载完整内容"""
log_file = PLAN_LOG_DIR / chat_id / filename
if not log_file.exists():
raise HTTPException(status_code=404, detail="日志文件不存在")
try:
with open(log_file, "r", encoding="utf-8") as f:
data = json.load(f)
return PlanLogDetail(**data)
except Exception as e:
raise HTTPException(status_code=500, detail=f"读取日志失败: {str(e)}") from e
# ========== 兼容旧接口 ==========
@router.get("/stats")
async def get_planner_stats():
"""获取规划器统计信息 - 兼容旧接口"""
overview = await get_planner_overview()
# 获取最近10条计划的摘要
recent_plans = []
for chat in overview.chats[:5]: # 从最近5个聊天中获取
try:
chat_logs = await get_chat_plan_logs(chat.chat_id, page=1, page_size=2)
recent_plans.extend(chat_logs.data)
except Exception:
continue
# 按时间排序取前10
recent_plans.sort(key=lambda x: x.timestamp, reverse=True)
recent_plans = recent_plans[:10]
return {
"total_chats": overview.total_chats,
"total_plans": overview.total_plans,
"avg_plan_time_ms": 0,
"avg_llm_time_ms": 0,
"recent_plans": recent_plans,
}
@router.get("/chats")
async def get_chat_list():
"""获取所有聊天ID列表 - 兼容旧接口"""
overview = await get_planner_overview()
return [chat.chat_id for chat in overview.chats]
@router.get("/all-logs")
async def get_all_logs(page: int = Query(1, ge=1), page_size: int = Query(20, ge=1, le=100)):
"""获取所有规划日志 - 兼容旧接口"""
if not PLAN_LOG_DIR.exists():
return {"data": [], "total": 0, "page": page, "page_size": page_size}
# 收集所有文件
all_files = []
for chat_dir in PLAN_LOG_DIR.iterdir():
if chat_dir.is_dir():
all_files.extend((chat_dir.name, log_file) for log_file in chat_dir.glob("*.json"))
# 按时间戳排序
all_files.sort(key=lambda x: parse_timestamp_from_filename(x[1].name), reverse=True)
total = len(all_files)
offset = (page - 1) * page_size
page_files = all_files[offset : offset + page_size]
logs = []
for chat_id, log_file in page_files:
try:
with open(log_file, "r", encoding="utf-8") as f:
data = json.load(f)
reasoning = data.get("reasoning", "")
logs.append(
{
"chat_id": data.get("chat_id", chat_id),
"timestamp": data.get("timestamp", parse_timestamp_from_filename(log_file.name)),
"filename": log_file.name,
"action_count": len(data.get("actions", [])),
"total_plan_ms": data.get("timing", {}).get("total_plan_ms", 0),
"llm_duration_ms": data.get("timing", {}).get("llm_duration_ms", 0),
"reasoning_preview": reasoning[:100] if reasoning else "",
}
)
except Exception:
continue
return {"data": logs, "total": total, "page": page, "page_size": page_size}

View File

@@ -1,273 +0,0 @@
"""
回复器监控API
提供回复器日志数据的查询接口
性能优化:
1. 聊天摘要只统计文件数量和最新时间戳,不读取文件内容
2. 日志列表使用文件名解析时间戳,只在需要时读取完整内容
3. 详情按需加载
"""
import json
from pathlib import Path
from typing import Dict, List, Optional
from fastapi import APIRouter, Depends, HTTPException, Query
from pydantic import BaseModel
from src.webui.dependencies import require_auth
router = APIRouter(prefix="/api/replier", tags=["replier"], dependencies=[Depends(require_auth)])
# 回复器日志目录
REPLY_LOG_DIR = Path("logs/reply")
class ReplierChatSummary(BaseModel):
"""聊天摘要 - 轻量级,不读取文件内容"""
chat_id: str
reply_count: int
latest_timestamp: float
latest_filename: str
class ReplyLogSummary(BaseModel):
"""回复日志摘要"""
chat_id: str
timestamp: float
filename: str
model: str
success: bool
llm_ms: float
overall_ms: float
output_preview: str
class ReplyLogDetail(BaseModel):
"""回复日志详情"""
type: str
chat_id: str
timestamp: float
prompt: str
output: str
processed_output: List[str]
model: str
reasoning: str
think_level: int
timing: Dict
error: Optional[str] = None
success: bool
class ReplierOverview(BaseModel):
"""回复器总览 - 轻量级统计"""
total_chats: int
total_replies: int
chats: List[ReplierChatSummary]
class PaginatedReplyLogs(BaseModel):
"""分页的回复日志列表"""
data: List[ReplyLogSummary]
total: int
page: int
page_size: int
chat_id: str
def parse_timestamp_from_filename(filename: str) -> float:
"""从文件名解析时间戳: 1766497488220_af92bdb1.json -> 1766497488.220"""
try:
timestamp_str = filename.split("_")[0]
# 时间戳是毫秒级,需要转换为秒
return float(timestamp_str) / 1000
except (ValueError, IndexError):
return 0
@router.get("/overview", response_model=ReplierOverview)
async def get_replier_overview():
"""
获取回复器总览 - 轻量级接口
只统计文件数量,不读取文件内容
"""
if not REPLY_LOG_DIR.exists():
return ReplierOverview(total_chats=0, total_replies=0, chats=[])
chats = []
total_replies = 0
for chat_dir in REPLY_LOG_DIR.iterdir():
if not chat_dir.is_dir():
continue
# 只统计json文件数量
json_files = list(chat_dir.glob("*.json"))
reply_count = len(json_files)
total_replies += reply_count
if reply_count == 0:
continue
# 从文件名获取最新时间戳
latest_file = max(json_files, key=lambda f: parse_timestamp_from_filename(f.name))
latest_timestamp = parse_timestamp_from_filename(latest_file.name)
chats.append(
ReplierChatSummary(
chat_id=chat_dir.name,
reply_count=reply_count,
latest_timestamp=latest_timestamp,
latest_filename=latest_file.name,
)
)
# 按最新时间戳排序
chats.sort(key=lambda x: x.latest_timestamp, reverse=True)
return ReplierOverview(total_chats=len(chats), total_replies=total_replies, chats=chats)
@router.get("/chat/{chat_id}/logs", response_model=PaginatedReplyLogs)
async def get_chat_reply_logs(
chat_id: str,
page: int = Query(1, ge=1),
page_size: int = Query(20, ge=1, le=100),
search: Optional[str] = Query(None, description="搜索关键词,匹配提示词内容"),
):
"""
获取指定聊天的回复日志列表(分页)
需要读取文件内容获取摘要信息
支持搜索提示词内容
"""
chat_dir = REPLY_LOG_DIR / chat_id
if not chat_dir.exists():
return PaginatedReplyLogs(data=[], total=0, page=page, page_size=page_size, chat_id=chat_id)
# 先获取所有文件并按时间戳排序
json_files = list(chat_dir.glob("*.json"))
json_files.sort(key=lambda f: parse_timestamp_from_filename(f.name), reverse=True)
# 如果有搜索关键词,需要过滤文件
if search:
search_lower = search.lower()
filtered_files = []
for log_file in json_files:
try:
with open(log_file, "r", encoding="utf-8") as f:
data = json.load(f)
prompt = data.get("prompt", "")
if search_lower in prompt.lower():
filtered_files.append(log_file)
except Exception:
continue
json_files = filtered_files
total = len(json_files)
# 分页 - 只读取当前页的文件
offset = (page - 1) * page_size
page_files = json_files[offset : offset + page_size]
logs = []
for log_file in page_files:
try:
with open(log_file, "r", encoding="utf-8") as f:
data = json.load(f)
output = data.get("output", "")
logs.append(
ReplyLogSummary(
chat_id=data.get("chat_id", chat_id),
timestamp=data.get("timestamp", parse_timestamp_from_filename(log_file.name)),
filename=log_file.name,
model=data.get("model", ""),
success=data.get("success", True),
llm_ms=data.get("timing", {}).get("llm_ms", 0),
overall_ms=data.get("timing", {}).get("overall_ms", 0),
output_preview=output[:100] if output else "",
)
)
except Exception:
# 文件读取失败时使用文件名信息
logs.append(
ReplyLogSummary(
chat_id=chat_id,
timestamp=parse_timestamp_from_filename(log_file.name),
filename=log_file.name,
model="",
success=False,
llm_ms=0,
overall_ms=0,
output_preview="[读取失败]",
)
)
return PaginatedReplyLogs(data=logs, total=total, page=page, page_size=page_size, chat_id=chat_id)
@router.get("/log/{chat_id}/{filename}", response_model=ReplyLogDetail)
async def get_reply_log_detail(chat_id: str, filename: str):
"""获取回复日志详情 - 按需加载完整内容"""
log_file = REPLY_LOG_DIR / chat_id / filename
if not log_file.exists():
raise HTTPException(status_code=404, detail="日志文件不存在")
try:
with open(log_file, "r", encoding="utf-8") as f:
data = json.load(f)
return ReplyLogDetail(
type=data.get("type", "reply"),
chat_id=data.get("chat_id", chat_id),
timestamp=data.get("timestamp", 0),
prompt=data.get("prompt", ""),
output=data.get("output", ""),
processed_output=data.get("processed_output", []),
model=data.get("model", ""),
reasoning=data.get("reasoning", ""),
think_level=data.get("think_level", 0),
timing=data.get("timing", {}),
error=data.get("error"),
success=data.get("success", True),
)
except Exception as e:
raise HTTPException(status_code=500, detail=f"读取日志失败: {str(e)}") from e
# ========== 兼容接口 ==========
@router.get("/stats")
async def get_replier_stats():
"""获取回复器统计信息"""
overview = await get_replier_overview()
# 获取最近10条回复的摘要
recent_replies = []
for chat in overview.chats[:5]: # 从最近5个聊天中获取
try:
chat_logs = await get_chat_reply_logs(chat.chat_id, page=1, page_size=2)
recent_replies.extend(chat_logs.data)
except Exception:
continue
# 按时间排序取前10
recent_replies.sort(key=lambda x: x.timestamp, reverse=True)
recent_replies = recent_replies[:10]
return {
"total_chats": overview.total_chats,
"total_replies": overview.total_replies,
"recent_replies": recent_replies,
}
@router.get("/chats")
async def get_replier_chat_list():
"""获取所有聊天ID列表"""
overview = await get_replier_overview()
return [chat.chat_id for chat in overview.chats]