Undefined-bot 2.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- Undefined/__init__.py +3 -0
- Undefined/__main__.py +6 -0
- Undefined/ai.py +1215 -0
- Undefined/config.py +371 -0
- Undefined/end_summary_storage.py +48 -0
- Undefined/faq.py +244 -0
- Undefined/handlers.py +1247 -0
- Undefined/injection_response_agent.py +131 -0
- Undefined/main.py +126 -0
- Undefined/memory.py +120 -0
- Undefined/onebot.py +512 -0
- Undefined/rate_limit.py +130 -0
- Undefined/render.py +123 -0
- Undefined/scheduled_task_storage.py +88 -0
- Undefined/services/__init__.py +1 -0
- Undefined/services/queue_manager.py +206 -0
- Undefined/skills/README.md +53 -0
- Undefined/skills/__init__.py +10 -0
- Undefined/skills/agents/README.md +144 -0
- Undefined/skills/agents/__init__.py +116 -0
- Undefined/skills/agents/entertainment_agent/config.json +17 -0
- Undefined/skills/agents/entertainment_agent/handler.py +220 -0
- Undefined/skills/agents/entertainment_agent/intro.md +25 -0
- Undefined/skills/agents/entertainment_agent/prompt.md +20 -0
- Undefined/skills/agents/entertainment_agent/tools/__init__.py +1 -0
- Undefined/skills/agents/entertainment_agent/tools/ai_draw_one/config.json +34 -0
- Undefined/skills/agents/entertainment_agent/tools/ai_draw_one/handler.py +62 -0
- Undefined/skills/agents/entertainment_agent/tools/ai_study_helper/config.json +22 -0
- Undefined/skills/agents/entertainment_agent/tools/ai_study_helper/handler.py +35 -0
- Undefined/skills/agents/entertainment_agent/tools/get_current_time/config.json +12 -0
- Undefined/skills/agents/entertainment_agent/tools/get_current_time/handler.py +5 -0
- Undefined/skills/agents/entertainment_agent/tools/horoscope/config.json +24 -0
- Undefined/skills/agents/entertainment_agent/tools/horoscope/handler.py +141 -0
- Undefined/skills/agents/entertainment_agent/tools/minecraft_skin/config.json +43 -0
- Undefined/skills/agents/entertainment_agent/tools/minecraft_skin/handler.py +55 -0
- Undefined/skills/agents/entertainment_agent/tools/novel_search/config.json +25 -0
- Undefined/skills/agents/entertainment_agent/tools/novel_search/handler.py +31 -0
- Undefined/skills/agents/entertainment_agent/tools/renjian/config.json +12 -0
- Undefined/skills/agents/entertainment_agent/tools/renjian/handler.py +30 -0
- Undefined/skills/agents/entertainment_agent/tools/wenchang_dijun/config.json +12 -0
- Undefined/skills/agents/entertainment_agent/tools/wenchang_dijun/handler.py +44 -0
- Undefined/skills/agents/file_analysis_agent/__init__.py +1 -0
- Undefined/skills/agents/file_analysis_agent/config.json +21 -0
- Undefined/skills/agents/file_analysis_agent/handler.py +248 -0
- Undefined/skills/agents/file_analysis_agent/intro.md +22 -0
- Undefined/skills/agents/file_analysis_agent/prompt.md +36 -0
- Undefined/skills/agents/file_analysis_agent/tools/__init__.py +1 -0
- Undefined/skills/agents/file_analysis_agent/tools/analyze_code/config.json +17 -0
- Undefined/skills/agents/file_analysis_agent/tools/analyze_code/handler.py +427 -0
- Undefined/skills/agents/file_analysis_agent/tools/analyze_multimodal/config.json +25 -0
- Undefined/skills/agents/file_analysis_agent/tools/analyze_multimodal/handler.py +178 -0
- Undefined/skills/agents/file_analysis_agent/tools/cleanup_temp/config.json +16 -0
- Undefined/skills/agents/file_analysis_agent/tools/cleanup_temp/handler.py +35 -0
- Undefined/skills/agents/file_analysis_agent/tools/detect_file_type/config.json +17 -0
- Undefined/skills/agents/file_analysis_agent/tools/detect_file_type/handler.py +221 -0
- Undefined/skills/agents/file_analysis_agent/tools/download_file/config.json +21 -0
- Undefined/skills/agents/file_analysis_agent/tools/download_file/handler.py +124 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_archive/config.json +25 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_archive/handler.py +190 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_docx/config.json +17 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_docx/handler.py +78 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_pdf/config.json +21 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_pdf/handler.py +67 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_pptx/config.json +17 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_pptx/handler.py +73 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_xlsx/config.json +17 -0
- Undefined/skills/agents/file_analysis_agent/tools/extract_xlsx/handler.py +101 -0
- Undefined/skills/agents/file_analysis_agent/tools/get_current_time/config.json +12 -0
- Undefined/skills/agents/file_analysis_agent/tools/get_current_time/handler.py +5 -0
- Undefined/skills/agents/file_analysis_agent/tools/read_text_file/config.json +21 -0
- Undefined/skills/agents/file_analysis_agent/tools/read_text_file/handler.py +90 -0
- Undefined/skills/agents/info_agent/config.json +17 -0
- Undefined/skills/agents/info_agent/handler.py +220 -0
- Undefined/skills/agents/info_agent/intro.md +22 -0
- Undefined/skills/agents/info_agent/prompt.md +27 -0
- Undefined/skills/agents/info_agent/tools/__init__.py +1 -0
- Undefined/skills/agents/info_agent/tools/baiduhot/config.json +18 -0
- Undefined/skills/agents/info_agent/tools/baiduhot/handler.py +49 -0
- Undefined/skills/agents/info_agent/tools/base64/config.json +22 -0
- Undefined/skills/agents/info_agent/tools/base64/handler.py +44 -0
- Undefined/skills/agents/info_agent/tools/douyinhot/config.json +18 -0
- Undefined/skills/agents/info_agent/tools/douyinhot/handler.py +53 -0
- Undefined/skills/agents/info_agent/tools/get_current_time/config.json +12 -0
- Undefined/skills/agents/info_agent/tools/get_current_time/handler.py +5 -0
- Undefined/skills/agents/info_agent/tools/gold_price/config.json +12 -0
- Undefined/skills/agents/info_agent/tools/gold_price/handler.py +58 -0
- Undefined/skills/agents/info_agent/tools/hash/config.json +22 -0
- Undefined/skills/agents/info_agent/tools/hash/handler.py +43 -0
- Undefined/skills/agents/info_agent/tools/history/config.json +12 -0
- Undefined/skills/agents/info_agent/tools/history/handler.py +37 -0
- Undefined/skills/agents/info_agent/tools/net_check/config.json +17 -0
- Undefined/skills/agents/info_agent/tools/net_check/handler.py +117 -0
- Undefined/skills/agents/info_agent/tools/news_tencent/config.json +17 -0
- Undefined/skills/agents/info_agent/tools/news_tencent/handler.py +38 -0
- Undefined/skills/agents/info_agent/tools/qq_level_query/config.json +29 -0
- Undefined/skills/agents/info_agent/tools/qq_level_query/handler.py +48 -0
- Undefined/skills/agents/info_agent/tools/speed/config.json +17 -0
- Undefined/skills/agents/info_agent/tools/speed/handler.py +37 -0
- Undefined/skills/agents/info_agent/tools/tcping/config.json +21 -0
- Undefined/skills/agents/info_agent/tools/tcping/handler.py +53 -0
- Undefined/skills/agents/info_agent/tools/weather_query/config.json +22 -0
- Undefined/skills/agents/info_agent/tools/weather_query/handler.py +207 -0
- Undefined/skills/agents/info_agent/tools/weibohot/config.json +18 -0
- Undefined/skills/agents/info_agent/tools/weibohot/handler.py +49 -0
- Undefined/skills/agents/info_agent/tools/whois/config.json +17 -0
- Undefined/skills/agents/info_agent/tools/whois/handler.py +63 -0
- Undefined/skills/agents/naga_code_analysis_agent/config.json +17 -0
- Undefined/skills/agents/naga_code_analysis_agent/handler.py +222 -0
- Undefined/skills/agents/naga_code_analysis_agent/intro.md +17 -0
- Undefined/skills/agents/naga_code_analysis_agent/prompt.md +19 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/__init__.py +1 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/get_current_time/config.json +12 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/get_current_time/handler.py +5 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/glob/config.json +17 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/glob/handler.py +37 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/list_directory/config.json +17 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/list_directory/handler.py +31 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/read_file/config.json +17 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/read_file/handler.py +66 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/read_naga_intro/config.json +12 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/read_naga_intro/handler.py +327 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/search_file_content/config.json +25 -0
- Undefined/skills/agents/naga_code_analysis_agent/tools/search_file_content/handler.py +46 -0
- Undefined/skills/agents/scheduler_agent/__init__.py +1 -0
- Undefined/skills/agents/scheduler_agent/config.json +17 -0
- Undefined/skills/agents/scheduler_agent/handler.py +218 -0
- Undefined/skills/agents/scheduler_agent/intro.md +17 -0
- Undefined/skills/agents/scheduler_agent/prompt.md +67 -0
- Undefined/skills/agents/scheduler_agent/tools/__init__.py +1 -0
- Undefined/skills/agents/scheduler_agent/tools/create_schedule_task/config.json +37 -0
- Undefined/skills/agents/scheduler_agent/tools/create_schedule_task/handler.py +68 -0
- Undefined/skills/agents/scheduler_agent/tools/delete_schedule_task/config.json +19 -0
- Undefined/skills/agents/scheduler_agent/tools/delete_schedule_task/handler.py +26 -0
- Undefined/skills/agents/scheduler_agent/tools/get_current_time/config.json +12 -0
- Undefined/skills/agents/scheduler_agent/tools/get_current_time/handler.py +5 -0
- Undefined/skills/agents/scheduler_agent/tools/list_schedule_tasks/config.json +11 -0
- Undefined/skills/agents/scheduler_agent/tools/list_schedule_tasks/handler.py +47 -0
- Undefined/skills/agents/scheduler_agent/tools/update_schedule_task/config.json +39 -0
- Undefined/skills/agents/scheduler_agent/tools/update_schedule_task/handler.py +46 -0
- Undefined/skills/agents/social_agent/config.json +17 -0
- Undefined/skills/agents/social_agent/handler.py +220 -0
- Undefined/skills/agents/social_agent/intro.md +17 -0
- Undefined/skills/agents/social_agent/prompt.md +19 -0
- Undefined/skills/agents/social_agent/tools/__init__.py +1 -0
- Undefined/skills/agents/social_agent/tools/bilibili_search/config.json +21 -0
- Undefined/skills/agents/social_agent/tools/bilibili_search/handler.py +68 -0
- Undefined/skills/agents/social_agent/tools/bilibili_user_info/config.json +17 -0
- Undefined/skills/agents/social_agent/tools/bilibili_user_info/handler.py +68 -0
- Undefined/skills/agents/social_agent/tools/get_current_time/config.json +12 -0
- Undefined/skills/agents/social_agent/tools/get_current_time/handler.py +5 -0
- Undefined/skills/agents/social_agent/tools/music_global_search/config.json +21 -0
- Undefined/skills/agents/social_agent/tools/music_global_search/handler.py +47 -0
- Undefined/skills/agents/social_agent/tools/music_info_get/config.json +22 -0
- Undefined/skills/agents/social_agent/tools/music_info_get/handler.py +35 -0
- Undefined/skills/agents/social_agent/tools/music_lyrics/config.json +22 -0
- Undefined/skills/agents/social_agent/tools/music_lyrics/handler.py +26 -0
- Undefined/skills/agents/social_agent/tools/video_random_recommend/config.json +21 -0
- Undefined/skills/agents/social_agent/tools/video_random_recommend/handler.py +21 -0
- Undefined/skills/agents/web_agent/config.json +17 -0
- Undefined/skills/agents/web_agent/handler.py +221 -0
- Undefined/skills/agents/web_agent/intro.md +14 -0
- Undefined/skills/agents/web_agent/prompt.md +16 -0
- Undefined/skills/agents/web_agent/tools/__init__.py +1 -0
- Undefined/skills/agents/web_agent/tools/crawl_webpage/config.json +21 -0
- Undefined/skills/agents/web_agent/tools/crawl_webpage/handler.py +102 -0
- Undefined/skills/agents/web_agent/tools/get_current_time/config.json +12 -0
- Undefined/skills/agents/web_agent/tools/get_current_time/handler.py +5 -0
- Undefined/skills/agents/web_agent/tools/web_search/config.json +21 -0
- Undefined/skills/agents/web_agent/tools/web_search/handler.py +29 -0
- Undefined/skills/tools/README.md +85 -0
- Undefined/skills/tools/__init__.py +120 -0
- Undefined/skills/tools/debug/config.json +17 -0
- Undefined/skills/tools/debug/handler.py +35 -0
- Undefined/skills/tools/end/config.json +17 -0
- Undefined/skills/tools/end/handler.py +24 -0
- Undefined/skills/tools/get_current_time/config.json +12 -0
- Undefined/skills/tools/get_current_time/handler.py +5 -0
- Undefined/skills/tools/get_forward_msg/config.json +17 -0
- Undefined/skills/tools/get_forward_msg/handler.py +131 -0
- Undefined/skills/tools/get_group_member_info/config.json +38 -0
- Undefined/skills/tools/get_group_member_info/handler.py +142 -0
- Undefined/skills/tools/get_messages_by_time/config.json +30 -0
- Undefined/skills/tools/get_messages_by_time/handler.py +128 -0
- Undefined/skills/tools/get_picture/config.json +45 -0
- Undefined/skills/tools/get_picture/handler.py +191 -0
- Undefined/skills/tools/get_recent_messages/config.json +30 -0
- Undefined/skills/tools/get_recent_messages/handler.py +88 -0
- Undefined/skills/tools/qq_like/config.json +22 -0
- Undefined/skills/tools/qq_like/handler.py +58 -0
- Undefined/skills/tools/render_html/config.json +26 -0
- Undefined/skills/tools/render_html/handler.py +39 -0
- Undefined/skills/tools/render_latex/config.json +26 -0
- Undefined/skills/tools/render_latex/handler.py +78 -0
- Undefined/skills/tools/render_markdown/config.json +26 -0
- Undefined/skills/tools/render_markdown/handler.py +63 -0
- Undefined/skills/tools/save_memory/config.json +17 -0
- Undefined/skills/tools/save_memory/handler.py +17 -0
- Undefined/skills/tools/send_message/config.json +21 -0
- Undefined/skills/tools/send_message/handler.py +60 -0
- Undefined/skills/tools/send_private_message/config.json +21 -0
- Undefined/skills/tools/send_private_message/handler.py +35 -0
- Undefined/utils/__init__.py +0 -0
- Undefined/utils/common.py +186 -0
- Undefined/utils/history.py +284 -0
- Undefined/utils/scheduler.py +286 -0
- Undefined/utils/sender.py +140 -0
- undefined_bot-2.1.0.dist-info/METADATA +259 -0
- undefined_bot-2.1.0.dist-info/RECORD +211 -0
- undefined_bot-2.1.0.dist-info/WHEEL +4 -0
- undefined_bot-2.1.0.dist-info/entry_points.txt +2 -0
- undefined_bot-2.1.0.dist-info/licenses/LICENSE +7 -0
Undefined/config.py
ADDED
|
@@ -0,0 +1,371 @@
|
|
|
1
|
+
"""配置加载模块"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
import os
|
|
6
|
+
from dataclasses import dataclass
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
from dotenv import load_dotenv
|
|
11
|
+
|
|
12
|
+
logger = logging.getLogger(__name__)
|
|
13
|
+
|
|
14
|
+
# 本地配置文件路径
|
|
15
|
+
LOCAL_CONFIG_PATH = Path("config.local.json")
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
@dataclass
|
|
19
|
+
class ChatModelConfig:
|
|
20
|
+
"""对话模型配置"""
|
|
21
|
+
|
|
22
|
+
api_url: str
|
|
23
|
+
api_key: str
|
|
24
|
+
model_name: str
|
|
25
|
+
max_tokens: int
|
|
26
|
+
thinking_enabled: bool = False # 是否启用 thinking
|
|
27
|
+
thinking_budget_tokens: int = 20000 # 思维预算 token 数量
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
@dataclass
|
|
31
|
+
class VisionModelConfig:
|
|
32
|
+
"""视觉模型配置"""
|
|
33
|
+
|
|
34
|
+
api_url: str
|
|
35
|
+
api_key: str
|
|
36
|
+
model_name: str
|
|
37
|
+
thinking_enabled: bool = False # 是否启用 thinking
|
|
38
|
+
thinking_budget_tokens: int = 20000 # 思维预算 token 数量
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@dataclass
|
|
42
|
+
class SecurityModelConfig:
|
|
43
|
+
"""安全模型配置(用于防注入检测和注入后的回复生成)"""
|
|
44
|
+
|
|
45
|
+
api_url: str
|
|
46
|
+
api_key: str
|
|
47
|
+
model_name: str
|
|
48
|
+
max_tokens: int
|
|
49
|
+
thinking_enabled: bool = False # 是否启用 thinking
|
|
50
|
+
thinking_budget_tokens: int = 0 # 思维预算 token 数量
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
@dataclass
|
|
54
|
+
class AgentModelConfig:
|
|
55
|
+
"""Agent 模型配置(用于执行 agents)"""
|
|
56
|
+
|
|
57
|
+
api_url: str
|
|
58
|
+
api_key: str
|
|
59
|
+
model_name: str
|
|
60
|
+
max_tokens: int = 4096
|
|
61
|
+
thinking_enabled: bool = False # 是否启用 thinking
|
|
62
|
+
thinking_budget_tokens: int = 0 # 思维预算 token 数量
|
|
63
|
+
|
|
64
|
+
|
|
65
|
+
def load_local_admins() -> list[int]:
|
|
66
|
+
"""从本地配置文件加载动态管理员列表"""
|
|
67
|
+
if not LOCAL_CONFIG_PATH.exists():
|
|
68
|
+
return []
|
|
69
|
+
|
|
70
|
+
try:
|
|
71
|
+
with open(LOCAL_CONFIG_PATH, "r", encoding="utf-8") as f:
|
|
72
|
+
data = json.load(f)
|
|
73
|
+
admin_qqs: list[int] = data.get("admin_qqs", [])
|
|
74
|
+
return admin_qqs
|
|
75
|
+
except Exception as e:
|
|
76
|
+
logger.warning(f"读取本地配置失败: {e}")
|
|
77
|
+
return []
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def save_local_admins(admin_qqs: list[int]) -> None:
|
|
81
|
+
"""保存动态管理员列表到本地配置文件"""
|
|
82
|
+
try:
|
|
83
|
+
data: dict[str, list[int]] = {}
|
|
84
|
+
if LOCAL_CONFIG_PATH.exists():
|
|
85
|
+
with open(LOCAL_CONFIG_PATH, "r", encoding="utf-8") as f:
|
|
86
|
+
data = json.load(f)
|
|
87
|
+
|
|
88
|
+
data["admin_qqs"] = admin_qqs
|
|
89
|
+
|
|
90
|
+
with open(LOCAL_CONFIG_PATH, "w", encoding="utf-8") as f:
|
|
91
|
+
json.dump(data, f, ensure_ascii=False, indent=2)
|
|
92
|
+
|
|
93
|
+
logger.info(f"已保存管理员列表到 {LOCAL_CONFIG_PATH}")
|
|
94
|
+
except Exception as e:
|
|
95
|
+
logger.error(f"保存本地配置失败: {e}")
|
|
96
|
+
raise
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class Config:
|
|
101
|
+
"""应用配置"""
|
|
102
|
+
|
|
103
|
+
bot_qq: int
|
|
104
|
+
superadmin_qq: int # 超级管理员(唯一)
|
|
105
|
+
admin_qqs: list[int] # 管理员列表(来自 .env + 本地配置)
|
|
106
|
+
forward_proxy_qq: int | None # 音频转发代理QQ号(可选,用于群聊音频转发)
|
|
107
|
+
onebot_ws_url: str
|
|
108
|
+
onebot_token: str
|
|
109
|
+
chat_model: ChatModelConfig
|
|
110
|
+
vision_model: VisionModelConfig
|
|
111
|
+
security_model: SecurityModelConfig # 安全模型(防注入检测和回复生成)
|
|
112
|
+
agent_model: AgentModelConfig # Agent 模型(用于执行各种 Agent)
|
|
113
|
+
log_file_path: str
|
|
114
|
+
log_max_size: int
|
|
115
|
+
log_backup_count: int
|
|
116
|
+
|
|
117
|
+
@classmethod
|
|
118
|
+
def load(cls) -> "Config":
|
|
119
|
+
"""从环境变量和本地配置加载配置"""
|
|
120
|
+
load_dotenv()
|
|
121
|
+
|
|
122
|
+
# 验证必需的环境变量
|
|
123
|
+
required_vars = [
|
|
124
|
+
"BOT_QQ",
|
|
125
|
+
"SUPERADMIN_QQ",
|
|
126
|
+
"ONEBOT_WS_URL",
|
|
127
|
+
"CHAT_MODEL_API_URL",
|
|
128
|
+
"CHAT_MODEL_API_KEY",
|
|
129
|
+
"CHAT_MODEL_NAME",
|
|
130
|
+
"CHAT_MODEL_MAX_TOKENS",
|
|
131
|
+
"VISION_MODEL_API_URL",
|
|
132
|
+
"VISION_MODEL_API_KEY",
|
|
133
|
+
"VISION_MODEL_NAME",
|
|
134
|
+
"AGENT_MODEL_API_URL",
|
|
135
|
+
"AGENT_MODEL_API_KEY",
|
|
136
|
+
"AGENT_MODEL_NAME",
|
|
137
|
+
]
|
|
138
|
+
|
|
139
|
+
missing = [var for var in required_vars if not os.getenv(var)]
|
|
140
|
+
if missing:
|
|
141
|
+
raise ValueError(f"缺少必需的环境变量: {', '.join(missing)}")
|
|
142
|
+
|
|
143
|
+
chat_model = ChatModelConfig(
|
|
144
|
+
api_url=os.getenv("CHAT_MODEL_API_URL", ""),
|
|
145
|
+
api_key=os.getenv("CHAT_MODEL_API_KEY", ""),
|
|
146
|
+
model_name=os.getenv("CHAT_MODEL_NAME", ""),
|
|
147
|
+
max_tokens=int(os.getenv("CHAT_MODEL_MAX_TOKENS", "8000")),
|
|
148
|
+
thinking_enabled=os.getenv("CHAT_MODEL_THINKING_ENABLED", "false").lower()
|
|
149
|
+
== "true",
|
|
150
|
+
thinking_budget_tokens=int(
|
|
151
|
+
os.getenv("CHAT_MODEL_THINKING_BUDGET_TOKENS", "20000")
|
|
152
|
+
),
|
|
153
|
+
)
|
|
154
|
+
|
|
155
|
+
vision_model = VisionModelConfig(
|
|
156
|
+
api_url=os.getenv("VISION_MODEL_API_URL", ""),
|
|
157
|
+
api_key=os.getenv("VISION_MODEL_API_KEY", ""),
|
|
158
|
+
model_name=os.getenv("VISION_MODEL_NAME", ""),
|
|
159
|
+
thinking_enabled=os.getenv("VISION_MODEL_THINKING_ENABLED", "false").lower()
|
|
160
|
+
== "true",
|
|
161
|
+
thinking_budget_tokens=int(
|
|
162
|
+
os.getenv("VISION_MODEL_THINKING_BUDGET_TOKENS", "20000")
|
|
163
|
+
),
|
|
164
|
+
)
|
|
165
|
+
|
|
166
|
+
# 安全模型配置(防注入检测和回复生成)
|
|
167
|
+
# 如果没有配置,则使用对话模型作为后备
|
|
168
|
+
security_model_api_url = os.getenv("SECURITY_MODEL_API_URL", "")
|
|
169
|
+
security_model_api_key = os.getenv("SECURITY_MODEL_API_KEY", "")
|
|
170
|
+
security_model_name = os.getenv("SECURITY_MODEL_NAME", "")
|
|
171
|
+
|
|
172
|
+
if security_model_api_url and security_model_api_key and security_model_name:
|
|
173
|
+
# 使用独立的安全模型配置
|
|
174
|
+
security_model = SecurityModelConfig(
|
|
175
|
+
api_url=security_model_api_url,
|
|
176
|
+
api_key=security_model_api_key,
|
|
177
|
+
model_name=security_model_name,
|
|
178
|
+
max_tokens=int(os.getenv("SECURITY_MODEL_MAX_TOKENS", "100")),
|
|
179
|
+
thinking_enabled=os.getenv(
|
|
180
|
+
"SECURITY_MODEL_THINKING_ENABLED", "false"
|
|
181
|
+
).lower()
|
|
182
|
+
== "true",
|
|
183
|
+
thinking_budget_tokens=int(
|
|
184
|
+
os.getenv("SECURITY_MODEL_THINKING_BUDGET_TOKENS", "0")
|
|
185
|
+
),
|
|
186
|
+
)
|
|
187
|
+
else:
|
|
188
|
+
# 如果没有配置,则使用对话模型作为后备
|
|
189
|
+
logger.warning("未配置安全模型,将使用对话模型作为后备")
|
|
190
|
+
security_model = SecurityModelConfig(
|
|
191
|
+
api_url=chat_model.api_url,
|
|
192
|
+
api_key=chat_model.api_key,
|
|
193
|
+
model_name=chat_model.model_name,
|
|
194
|
+
max_tokens=chat_model.max_tokens,
|
|
195
|
+
thinking_enabled=False,
|
|
196
|
+
thinking_budget_tokens=0,
|
|
197
|
+
)
|
|
198
|
+
|
|
199
|
+
# Agent 模型配置
|
|
200
|
+
agent_model = AgentModelConfig(
|
|
201
|
+
api_url=os.getenv("AGENT_MODEL_API_URL", ""),
|
|
202
|
+
api_key=os.getenv("AGENT_MODEL_API_KEY", ""),
|
|
203
|
+
model_name=os.getenv("AGENT_MODEL_NAME", ""),
|
|
204
|
+
max_tokens=int(os.getenv("AGENT_MODEL_MAX_TOKENS", "4096")),
|
|
205
|
+
thinking_enabled=os.getenv("AGENT_MODEL_THINKING_ENABLED", "false").lower()
|
|
206
|
+
== "true",
|
|
207
|
+
thinking_budget_tokens=int(
|
|
208
|
+
os.getenv("AGENT_MODEL_THINKING_BUDGET_TOKENS", "0")
|
|
209
|
+
),
|
|
210
|
+
)
|
|
211
|
+
|
|
212
|
+
# 解析超级管理员
|
|
213
|
+
superadmin_qq = int(os.getenv("SUPERADMIN_QQ", "0"))
|
|
214
|
+
|
|
215
|
+
# 解析 .env 中的管理员列表
|
|
216
|
+
admin_qq_str = os.getenv("ADMIN_QQ", "")
|
|
217
|
+
env_admins: list[int] = []
|
|
218
|
+
if admin_qq_str:
|
|
219
|
+
try:
|
|
220
|
+
env_admins = [
|
|
221
|
+
int(qq.strip()) for qq in admin_qq_str.split(",") if qq.strip()
|
|
222
|
+
]
|
|
223
|
+
except ValueError:
|
|
224
|
+
raise ValueError("ADMIN_QQ 格式错误,应为逗号分隔的数字")
|
|
225
|
+
|
|
226
|
+
# 合并本地配置的管理员
|
|
227
|
+
local_admins = load_local_admins()
|
|
228
|
+
all_admins = list(set(env_admins + local_admins))
|
|
229
|
+
|
|
230
|
+
# 确保超级管理员也在管理员列表中
|
|
231
|
+
if superadmin_qq and superadmin_qq not in all_admins:
|
|
232
|
+
all_admins.append(superadmin_qq)
|
|
233
|
+
|
|
234
|
+
# 日志配置
|
|
235
|
+
log_file_path = os.getenv("LOG_FILE_PATH", "logs/bot.log")
|
|
236
|
+
log_max_size = (
|
|
237
|
+
int(os.getenv("LOG_MAX_SIZE_MB", "10")) * 1024 * 1024
|
|
238
|
+
) # 转换为字节
|
|
239
|
+
log_backup_count = int(os.getenv("LOG_BACKUP_COUNT", "5"))
|
|
240
|
+
|
|
241
|
+
# 音频转发代理QQ号(可选)
|
|
242
|
+
forward_proxy_qq_str = os.getenv("FORWARD_PROXY_QQ")
|
|
243
|
+
forward_proxy_qq = None
|
|
244
|
+
if forward_proxy_qq_str and forward_proxy_qq_str.strip():
|
|
245
|
+
try:
|
|
246
|
+
forward_proxy_qq = int(forward_proxy_qq_str.strip())
|
|
247
|
+
except ValueError:
|
|
248
|
+
logger.warning(f"FORWARD_PROXY_QQ 格式错误: {forward_proxy_qq_str}")
|
|
249
|
+
|
|
250
|
+
return cls(
|
|
251
|
+
bot_qq=int(os.getenv("BOT_QQ", "0")),
|
|
252
|
+
superadmin_qq=superadmin_qq,
|
|
253
|
+
admin_qqs=all_admins,
|
|
254
|
+
forward_proxy_qq=forward_proxy_qq,
|
|
255
|
+
onebot_ws_url=os.getenv("ONEBOT_WS_URL", ""),
|
|
256
|
+
onebot_token=os.getenv("ONEBOT_TOKEN", ""),
|
|
257
|
+
chat_model=chat_model,
|
|
258
|
+
vision_model=vision_model,
|
|
259
|
+
security_model=security_model,
|
|
260
|
+
agent_model=agent_model,
|
|
261
|
+
log_file_path=log_file_path,
|
|
262
|
+
log_max_size=log_max_size,
|
|
263
|
+
log_backup_count=log_backup_count,
|
|
264
|
+
)
|
|
265
|
+
|
|
266
|
+
def reload(self) -> None:
|
|
267
|
+
"""热重载配置(重新加载管理员列表等动态配置)"""
|
|
268
|
+
# 重新加载本地管理员
|
|
269
|
+
local_admins = load_local_admins()
|
|
270
|
+
|
|
271
|
+
# 重新读取 .env 中的管理员
|
|
272
|
+
load_dotenv(override=True)
|
|
273
|
+
admin_qq_str = os.getenv("ADMIN_QQ", "")
|
|
274
|
+
env_admins: list[int] = []
|
|
275
|
+
if admin_qq_str:
|
|
276
|
+
try:
|
|
277
|
+
env_admins = [
|
|
278
|
+
int(qq.strip()) for qq in admin_qq_str.split(",") if qq.strip()
|
|
279
|
+
]
|
|
280
|
+
except ValueError:
|
|
281
|
+
logger.warning("ADMIN_QQ 格式错误")
|
|
282
|
+
|
|
283
|
+
# 合并管理员列表
|
|
284
|
+
all_admins = list(set(env_admins + local_admins))
|
|
285
|
+
if self.superadmin_qq and self.superadmin_qq not in all_admins:
|
|
286
|
+
all_admins.append(self.superadmin_qq)
|
|
287
|
+
|
|
288
|
+
self.admin_qqs = all_admins
|
|
289
|
+
logger.info(f"配置已重载,管理员: {self.admin_qqs}")
|
|
290
|
+
|
|
291
|
+
def add_admin(self, qq: int) -> bool:
|
|
292
|
+
"""添加管理员(保存到本地配置)
|
|
293
|
+
|
|
294
|
+
参数:
|
|
295
|
+
qq: 要添加的 QQ 号
|
|
296
|
+
|
|
297
|
+
返回:
|
|
298
|
+
是否添加成功(已存在返回 False)
|
|
299
|
+
"""
|
|
300
|
+
if qq in self.admin_qqs:
|
|
301
|
+
return False
|
|
302
|
+
|
|
303
|
+
self.admin_qqs.append(qq)
|
|
304
|
+
|
|
305
|
+
# 获取当前本地管理员并添加新的
|
|
306
|
+
local_admins = load_local_admins()
|
|
307
|
+
if qq not in local_admins:
|
|
308
|
+
local_admins.append(qq)
|
|
309
|
+
save_local_admins(local_admins)
|
|
310
|
+
|
|
311
|
+
return True
|
|
312
|
+
|
|
313
|
+
def remove_admin(self, qq: int) -> bool:
|
|
314
|
+
"""移除管理员(从本地配置中移除)
|
|
315
|
+
|
|
316
|
+
参数:
|
|
317
|
+
qq: 要移除的 QQ 号
|
|
318
|
+
|
|
319
|
+
返回:
|
|
320
|
+
是否移除成功
|
|
321
|
+
"""
|
|
322
|
+
# 不能移除超级管理员
|
|
323
|
+
if qq == self.superadmin_qq:
|
|
324
|
+
return False
|
|
325
|
+
|
|
326
|
+
if qq not in self.admin_qqs:
|
|
327
|
+
return False
|
|
328
|
+
|
|
329
|
+
self.admin_qqs.remove(qq)
|
|
330
|
+
|
|
331
|
+
# 从本地配置中移除
|
|
332
|
+
local_admins = load_local_admins()
|
|
333
|
+
if qq in local_admins:
|
|
334
|
+
local_admins.remove(qq)
|
|
335
|
+
save_local_admins(local_admins)
|
|
336
|
+
|
|
337
|
+
return True
|
|
338
|
+
|
|
339
|
+
def is_superadmin(self, qq: int) -> bool:
|
|
340
|
+
"""检查是否为超级管理员
|
|
341
|
+
|
|
342
|
+
参数:
|
|
343
|
+
qq: QQ 号
|
|
344
|
+
|
|
345
|
+
返回:
|
|
346
|
+
是否为超级管理员
|
|
347
|
+
"""
|
|
348
|
+
return qq == self.superadmin_qq
|
|
349
|
+
|
|
350
|
+
def is_admin(self, qq: int) -> bool:
|
|
351
|
+
"""检查是否为管理员
|
|
352
|
+
|
|
353
|
+
参数:
|
|
354
|
+
qq: QQ 号
|
|
355
|
+
|
|
356
|
+
返回:
|
|
357
|
+
是否为管理员
|
|
358
|
+
"""
|
|
359
|
+
return qq in self.admin_qqs
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
# 全局配置实例
|
|
363
|
+
_config: Optional[Config] = None
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def get_config() -> Config:
|
|
367
|
+
"""获取配置实例(单例模式)"""
|
|
368
|
+
global _config
|
|
369
|
+
if _config is None:
|
|
370
|
+
_config = Config.load()
|
|
371
|
+
return _config
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"""End 摘要持久化存储模块"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import List
|
|
7
|
+
|
|
8
|
+
logger = logging.getLogger(__name__)
|
|
9
|
+
|
|
10
|
+
# End 摘要数据存储路径
|
|
11
|
+
END_SUMMARIES_FILE_PATH = Path("data/end_summaries.json")
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class EndSummaryStorage:
|
|
15
|
+
"""End 摘要存储管理器"""
|
|
16
|
+
|
|
17
|
+
def __init__(self) -> None:
|
|
18
|
+
"""初始化存储"""
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
def load(self) -> List[str]:
|
|
22
|
+
"""从文件加载所有摘要"""
|
|
23
|
+
if not END_SUMMARIES_FILE_PATH.exists():
|
|
24
|
+
return []
|
|
25
|
+
|
|
26
|
+
try:
|
|
27
|
+
with open(END_SUMMARIES_FILE_PATH, "r", encoding="utf-8") as f:
|
|
28
|
+
data = json.load(f)
|
|
29
|
+
if isinstance(data, list):
|
|
30
|
+
return data
|
|
31
|
+
else:
|
|
32
|
+
logger.warning(
|
|
33
|
+
f"End 摘要数据格式异常,期望 list,实际得到 {type(data)}"
|
|
34
|
+
)
|
|
35
|
+
return []
|
|
36
|
+
except Exception as e:
|
|
37
|
+
logger.error(f"加载 End 摘要数据失败: {e}")
|
|
38
|
+
return []
|
|
39
|
+
|
|
40
|
+
def save(self, summaries: List[str]) -> None:
|
|
41
|
+
"""保存所有摘要到文件"""
|
|
42
|
+
try:
|
|
43
|
+
END_SUMMARIES_FILE_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
44
|
+
with open(END_SUMMARIES_FILE_PATH, "w", encoding="utf-8") as f:
|
|
45
|
+
json.dump(summaries, f, ensure_ascii=False, indent=2)
|
|
46
|
+
logger.debug(f"已保存 {len(summaries)} 条 End 摘要")
|
|
47
|
+
except Exception as e:
|
|
48
|
+
logger.error(f"保存 End 摘要数据失败: {e}")
|
Undefined/faq.py
ADDED
|
@@ -0,0 +1,244 @@
|
|
|
1
|
+
"""FAQ 存储管理"""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
import logging
|
|
5
|
+
from dataclasses import dataclass, asdict
|
|
6
|
+
from datetime import datetime
|
|
7
|
+
from pathlib import Path
|
|
8
|
+
from typing import Optional
|
|
9
|
+
|
|
10
|
+
logger = logging.getLogger(__name__)
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
@dataclass
|
|
14
|
+
class FAQ:
|
|
15
|
+
"""FAQ 条目"""
|
|
16
|
+
|
|
17
|
+
id: str
|
|
18
|
+
group_id: int
|
|
19
|
+
target_qq: int
|
|
20
|
+
start_time: str
|
|
21
|
+
end_time: str
|
|
22
|
+
created_at: str
|
|
23
|
+
title: str
|
|
24
|
+
content: str
|
|
25
|
+
|
|
26
|
+
def to_dict(self) -> dict[str, str | int]:
|
|
27
|
+
"""转换为字典"""
|
|
28
|
+
return asdict(self)
|
|
29
|
+
|
|
30
|
+
@classmethod
|
|
31
|
+
def from_dict(cls, data: dict[str, str | int]) -> "FAQ":
|
|
32
|
+
"""从字典创建 FAQ"""
|
|
33
|
+
return cls(**data) # type: ignore
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
class FAQStorage:
|
|
37
|
+
"""FAQ 本地存储管理"""
|
|
38
|
+
|
|
39
|
+
def __init__(self, base_dir: str = "data/faq") -> None:
|
|
40
|
+
self.base_dir = Path(base_dir)
|
|
41
|
+
self.base_dir.mkdir(parents=True, exist_ok=True)
|
|
42
|
+
|
|
43
|
+
def _get_group_dir(self, group_id: int) -> Path:
|
|
44
|
+
"""获取群组的 FAQ 存储目录
|
|
45
|
+
|
|
46
|
+
参数:
|
|
47
|
+
group_id: 群号
|
|
48
|
+
|
|
49
|
+
返回:
|
|
50
|
+
群组 FAQ 目录路径
|
|
51
|
+
"""
|
|
52
|
+
group_dir = self.base_dir / str(group_id)
|
|
53
|
+
group_dir.mkdir(parents=True, exist_ok=True)
|
|
54
|
+
return group_dir
|
|
55
|
+
|
|
56
|
+
def _generate_id(self, group_id: int) -> str:
|
|
57
|
+
"""生成新的 FAQ ID
|
|
58
|
+
|
|
59
|
+
参数:
|
|
60
|
+
group_id: 群号
|
|
61
|
+
|
|
62
|
+
返回:
|
|
63
|
+
新的 FAQ ID
|
|
64
|
+
"""
|
|
65
|
+
group_dir = self._get_group_dir(group_id)
|
|
66
|
+
existing = list(group_dir.glob("*.json"))
|
|
67
|
+
|
|
68
|
+
# 使用时间戳 + 序号
|
|
69
|
+
timestamp = datetime.now().strftime("%Y%m%d")
|
|
70
|
+
count = sum(1 for f in existing if f.stem.startswith(timestamp))
|
|
71
|
+
return f"{timestamp}-{count + 1:03d}"
|
|
72
|
+
|
|
73
|
+
def save(self, faq: FAQ) -> str:
|
|
74
|
+
"""保存 FAQ
|
|
75
|
+
|
|
76
|
+
参数:
|
|
77
|
+
faq: FAQ 条目
|
|
78
|
+
|
|
79
|
+
返回:
|
|
80
|
+
FAQ ID
|
|
81
|
+
"""
|
|
82
|
+
group_dir = self._get_group_dir(faq.group_id)
|
|
83
|
+
file_path = group_dir / f"{faq.id}.json"
|
|
84
|
+
|
|
85
|
+
with open(file_path, "w", encoding="utf-8") as f:
|
|
86
|
+
json.dump(faq.to_dict(), f, ensure_ascii=False, indent=2)
|
|
87
|
+
|
|
88
|
+
logger.info(f"FAQ 已保存: {file_path}")
|
|
89
|
+
return faq.id
|
|
90
|
+
|
|
91
|
+
def create(
|
|
92
|
+
self,
|
|
93
|
+
group_id: int,
|
|
94
|
+
target_qq: int,
|
|
95
|
+
start_time: str,
|
|
96
|
+
end_time: str,
|
|
97
|
+
title: str,
|
|
98
|
+
content: str,
|
|
99
|
+
) -> FAQ:
|
|
100
|
+
"""创建新的 FAQ 条目
|
|
101
|
+
|
|
102
|
+
参数:
|
|
103
|
+
group_id: 群号
|
|
104
|
+
target_qq: 目标 QQ 号
|
|
105
|
+
start_time: 开始时间
|
|
106
|
+
end_time: 结束时间
|
|
107
|
+
title: 标题
|
|
108
|
+
content: 内容
|
|
109
|
+
|
|
110
|
+
返回:
|
|
111
|
+
创建的 FAQ 对象
|
|
112
|
+
"""
|
|
113
|
+
faq = FAQ(
|
|
114
|
+
id=self._generate_id(group_id),
|
|
115
|
+
group_id=group_id,
|
|
116
|
+
target_qq=target_qq,
|
|
117
|
+
start_time=start_time,
|
|
118
|
+
end_time=end_time,
|
|
119
|
+
created_at=datetime.now().isoformat(),
|
|
120
|
+
title=title,
|
|
121
|
+
content=content,
|
|
122
|
+
)
|
|
123
|
+
self.save(faq)
|
|
124
|
+
return faq
|
|
125
|
+
|
|
126
|
+
def get(self, group_id: int, faq_id: str) -> Optional[FAQ]:
|
|
127
|
+
"""获取指定 FAQ
|
|
128
|
+
|
|
129
|
+
参数:
|
|
130
|
+
group_id: 群号
|
|
131
|
+
faq_id: FAQ ID
|
|
132
|
+
|
|
133
|
+
返回:
|
|
134
|
+
FAQ 对象,不存在则返回 None
|
|
135
|
+
"""
|
|
136
|
+
group_dir = self._get_group_dir(group_id)
|
|
137
|
+
file_path = group_dir / f"{faq_id}.json"
|
|
138
|
+
|
|
139
|
+
if not file_path.exists():
|
|
140
|
+
return None
|
|
141
|
+
|
|
142
|
+
try:
|
|
143
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
144
|
+
data = json.load(f)
|
|
145
|
+
return FAQ.from_dict(data)
|
|
146
|
+
except Exception as e:
|
|
147
|
+
logger.error(f"读取 FAQ 失败: {e}")
|
|
148
|
+
return None
|
|
149
|
+
|
|
150
|
+
def list_all(self, group_id: int) -> list[FAQ]:
|
|
151
|
+
"""列出群组的所有 FAQ
|
|
152
|
+
|
|
153
|
+
参数:
|
|
154
|
+
group_id: 群号
|
|
155
|
+
|
|
156
|
+
返回:
|
|
157
|
+
FAQ 列表
|
|
158
|
+
"""
|
|
159
|
+
group_dir = self._get_group_dir(group_id)
|
|
160
|
+
faqs: list[FAQ] = []
|
|
161
|
+
|
|
162
|
+
for file_path in sorted(group_dir.glob("*.json"), reverse=True):
|
|
163
|
+
try:
|
|
164
|
+
with open(file_path, "r", encoding="utf-8") as f:
|
|
165
|
+
data = json.load(f)
|
|
166
|
+
faqs.append(FAQ.from_dict(data))
|
|
167
|
+
except Exception as e:
|
|
168
|
+
logger.error(f"读取 FAQ 失败 {file_path}: {e}")
|
|
169
|
+
|
|
170
|
+
return faqs
|
|
171
|
+
|
|
172
|
+
def search(self, group_id: int, keyword: str) -> list[FAQ]:
|
|
173
|
+
"""搜索 FAQ
|
|
174
|
+
|
|
175
|
+
根据关键词在标题和内容中搜索匹配的 FAQ
|
|
176
|
+
|
|
177
|
+
参数:
|
|
178
|
+
group_id: 群号
|
|
179
|
+
keyword: 搜索关键词
|
|
180
|
+
|
|
181
|
+
返回:
|
|
182
|
+
匹配的 FAQ 列表
|
|
183
|
+
"""
|
|
184
|
+
keyword_lower = keyword.lower()
|
|
185
|
+
all_faqs = self.list_all(group_id)
|
|
186
|
+
|
|
187
|
+
matched: list[FAQ] = []
|
|
188
|
+
for faq in all_faqs:
|
|
189
|
+
# 在标题和内容中搜索
|
|
190
|
+
if (
|
|
191
|
+
keyword_lower in faq.title.lower()
|
|
192
|
+
or keyword_lower in faq.content.lower()
|
|
193
|
+
):
|
|
194
|
+
matched.append(faq)
|
|
195
|
+
|
|
196
|
+
return matched
|
|
197
|
+
|
|
198
|
+
def delete(self, group_id: int, faq_id: str) -> bool:
|
|
199
|
+
"""删除 FAQ
|
|
200
|
+
|
|
201
|
+
参数:
|
|
202
|
+
group_id: 群号
|
|
203
|
+
faq_id: FAQ ID
|
|
204
|
+
|
|
205
|
+
返回:
|
|
206
|
+
是否成功删除
|
|
207
|
+
"""
|
|
208
|
+
group_dir = self._get_group_dir(group_id)
|
|
209
|
+
file_path = group_dir / f"{faq_id}.json"
|
|
210
|
+
|
|
211
|
+
if file_path.exists():
|
|
212
|
+
file_path.unlink()
|
|
213
|
+
logger.info(f"FAQ 已删除: {file_path}")
|
|
214
|
+
return True
|
|
215
|
+
return False
|
|
216
|
+
|
|
217
|
+
|
|
218
|
+
def extract_faq_title(content: str) -> str:
|
|
219
|
+
"""从分析内容中提取 FAQ 标题
|
|
220
|
+
|
|
221
|
+
参数:
|
|
222
|
+
content: 分析内容
|
|
223
|
+
|
|
224
|
+
返回:
|
|
225
|
+
提取的标题
|
|
226
|
+
"""
|
|
227
|
+
# 尝试从 FAQ 条目中提取问题
|
|
228
|
+
for line in content.split("\n"):
|
|
229
|
+
line = line.strip()
|
|
230
|
+
if line.startswith("**问题**:") or line.startswith("**问题**:"):
|
|
231
|
+
title = line.split(":", 1)[-1].split(":", 1)[-1].strip()
|
|
232
|
+
return title[:100] # 限制长度
|
|
233
|
+
|
|
234
|
+
# 尝试从 Bug 问题描述中提取
|
|
235
|
+
in_bug_section = False
|
|
236
|
+
for line in content.split("\n"):
|
|
237
|
+
line = line.strip()
|
|
238
|
+
if "Bug 问题描述" in line:
|
|
239
|
+
in_bug_section = True
|
|
240
|
+
continue
|
|
241
|
+
if in_bug_section and line and not line.startswith("#"):
|
|
242
|
+
return line[:100]
|
|
243
|
+
|
|
244
|
+
return "未命名问题"
|