AstrBot 3.5.6__py3-none-any.whl → 4.7.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.
Files changed (288) hide show
  1. astrbot/api/__init__.py +16 -4
  2. astrbot/api/all.py +2 -1
  3. astrbot/api/event/__init__.py +5 -6
  4. astrbot/api/event/filter/__init__.py +37 -34
  5. astrbot/api/platform/__init__.py +7 -8
  6. astrbot/api/provider/__init__.py +8 -7
  7. astrbot/api/star/__init__.py +3 -4
  8. astrbot/api/util/__init__.py +2 -2
  9. astrbot/cli/__init__.py +1 -0
  10. astrbot/cli/__main__.py +18 -197
  11. astrbot/cli/commands/__init__.py +6 -0
  12. astrbot/cli/commands/cmd_conf.py +209 -0
  13. astrbot/cli/commands/cmd_init.py +56 -0
  14. astrbot/cli/commands/cmd_plug.py +245 -0
  15. astrbot/cli/commands/cmd_run.py +62 -0
  16. astrbot/cli/utils/__init__.py +18 -0
  17. astrbot/cli/utils/basic.py +76 -0
  18. astrbot/cli/utils/plugin.py +246 -0
  19. astrbot/cli/utils/version_comparator.py +90 -0
  20. astrbot/core/__init__.py +17 -19
  21. astrbot/core/agent/agent.py +14 -0
  22. astrbot/core/agent/handoff.py +38 -0
  23. astrbot/core/agent/hooks.py +30 -0
  24. astrbot/core/agent/mcp_client.py +385 -0
  25. astrbot/core/agent/message.py +175 -0
  26. astrbot/core/agent/response.py +14 -0
  27. astrbot/core/agent/run_context.py +22 -0
  28. astrbot/core/agent/runners/__init__.py +3 -0
  29. astrbot/core/agent/runners/base.py +65 -0
  30. astrbot/core/agent/runners/coze/coze_agent_runner.py +367 -0
  31. astrbot/core/agent/runners/coze/coze_api_client.py +324 -0
  32. astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py +403 -0
  33. astrbot/core/agent/runners/dify/dify_agent_runner.py +336 -0
  34. astrbot/core/agent/runners/dify/dify_api_client.py +195 -0
  35. astrbot/core/agent/runners/tool_loop_agent_runner.py +400 -0
  36. astrbot/core/agent/tool.py +285 -0
  37. astrbot/core/agent/tool_executor.py +17 -0
  38. astrbot/core/astr_agent_context.py +19 -0
  39. astrbot/core/astr_agent_hooks.py +36 -0
  40. astrbot/core/astr_agent_run_util.py +80 -0
  41. astrbot/core/astr_agent_tool_exec.py +246 -0
  42. astrbot/core/astrbot_config_mgr.py +275 -0
  43. astrbot/core/config/__init__.py +2 -2
  44. astrbot/core/config/astrbot_config.py +60 -20
  45. astrbot/core/config/default.py +1972 -453
  46. astrbot/core/config/i18n_utils.py +110 -0
  47. astrbot/core/conversation_mgr.py +285 -75
  48. astrbot/core/core_lifecycle.py +167 -62
  49. astrbot/core/db/__init__.py +305 -102
  50. astrbot/core/db/migration/helper.py +69 -0
  51. astrbot/core/db/migration/migra_3_to_4.py +357 -0
  52. astrbot/core/db/migration/migra_45_to_46.py +44 -0
  53. astrbot/core/db/migration/migra_webchat_session.py +131 -0
  54. astrbot/core/db/migration/shared_preferences_v3.py +48 -0
  55. astrbot/core/db/migration/sqlite_v3.py +497 -0
  56. astrbot/core/db/po.py +259 -55
  57. astrbot/core/db/sqlite.py +773 -528
  58. astrbot/core/db/vec_db/base.py +73 -0
  59. astrbot/core/db/vec_db/faiss_impl/__init__.py +3 -0
  60. astrbot/core/db/vec_db/faiss_impl/document_storage.py +392 -0
  61. astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +93 -0
  62. astrbot/core/db/vec_db/faiss_impl/sqlite_init.sql +17 -0
  63. astrbot/core/db/vec_db/faiss_impl/vec_db.py +204 -0
  64. astrbot/core/event_bus.py +26 -22
  65. astrbot/core/exceptions.py +9 -0
  66. astrbot/core/file_token_service.py +98 -0
  67. astrbot/core/initial_loader.py +19 -10
  68. astrbot/core/knowledge_base/chunking/__init__.py +9 -0
  69. astrbot/core/knowledge_base/chunking/base.py +25 -0
  70. astrbot/core/knowledge_base/chunking/fixed_size.py +59 -0
  71. astrbot/core/knowledge_base/chunking/recursive.py +161 -0
  72. astrbot/core/knowledge_base/kb_db_sqlite.py +301 -0
  73. astrbot/core/knowledge_base/kb_helper.py +642 -0
  74. astrbot/core/knowledge_base/kb_mgr.py +330 -0
  75. astrbot/core/knowledge_base/models.py +120 -0
  76. astrbot/core/knowledge_base/parsers/__init__.py +13 -0
  77. astrbot/core/knowledge_base/parsers/base.py +51 -0
  78. astrbot/core/knowledge_base/parsers/markitdown_parser.py +26 -0
  79. astrbot/core/knowledge_base/parsers/pdf_parser.py +101 -0
  80. astrbot/core/knowledge_base/parsers/text_parser.py +42 -0
  81. astrbot/core/knowledge_base/parsers/url_parser.py +103 -0
  82. astrbot/core/knowledge_base/parsers/util.py +13 -0
  83. astrbot/core/knowledge_base/prompts.py +65 -0
  84. astrbot/core/knowledge_base/retrieval/__init__.py +14 -0
  85. astrbot/core/knowledge_base/retrieval/hit_stopwords.txt +767 -0
  86. astrbot/core/knowledge_base/retrieval/manager.py +276 -0
  87. astrbot/core/knowledge_base/retrieval/rank_fusion.py +142 -0
  88. astrbot/core/knowledge_base/retrieval/sparse_retriever.py +136 -0
  89. astrbot/core/log.py +21 -15
  90. astrbot/core/message/components.py +413 -287
  91. astrbot/core/message/message_event_result.py +35 -24
  92. astrbot/core/persona_mgr.py +192 -0
  93. astrbot/core/pipeline/__init__.py +14 -14
  94. astrbot/core/pipeline/content_safety_check/stage.py +13 -9
  95. astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
  96. astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +13 -14
  97. astrbot/core/pipeline/content_safety_check/strategies/keywords.py +2 -1
  98. astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
  99. astrbot/core/pipeline/context.py +7 -1
  100. astrbot/core/pipeline/context_utils.py +107 -0
  101. astrbot/core/pipeline/preprocess_stage/stage.py +63 -36
  102. astrbot/core/pipeline/process_stage/method/agent_request.py +48 -0
  103. astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +464 -0
  104. astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +202 -0
  105. astrbot/core/pipeline/process_stage/method/star_request.py +26 -32
  106. astrbot/core/pipeline/process_stage/stage.py +21 -15
  107. astrbot/core/pipeline/process_stage/utils.py +125 -0
  108. astrbot/core/pipeline/rate_limit_check/stage.py +34 -36
  109. astrbot/core/pipeline/respond/stage.py +142 -101
  110. astrbot/core/pipeline/result_decorate/stage.py +124 -57
  111. astrbot/core/pipeline/scheduler.py +21 -16
  112. astrbot/core/pipeline/session_status_check/stage.py +37 -0
  113. astrbot/core/pipeline/stage.py +11 -76
  114. astrbot/core/pipeline/waking_check/stage.py +69 -33
  115. astrbot/core/pipeline/whitelist_check/stage.py +10 -7
  116. astrbot/core/platform/__init__.py +6 -6
  117. astrbot/core/platform/astr_message_event.py +107 -129
  118. astrbot/core/platform/astrbot_message.py +32 -12
  119. astrbot/core/platform/manager.py +62 -18
  120. astrbot/core/platform/message_session.py +30 -0
  121. astrbot/core/platform/platform.py +16 -24
  122. astrbot/core/platform/platform_metadata.py +9 -4
  123. astrbot/core/platform/register.py +12 -7
  124. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +136 -60
  125. astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +126 -46
  126. astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +63 -31
  127. astrbot/core/platform/sources/dingtalk/dingtalk_event.py +30 -26
  128. astrbot/core/platform/sources/discord/client.py +129 -0
  129. astrbot/core/platform/sources/discord/components.py +139 -0
  130. astrbot/core/platform/sources/discord/discord_platform_adapter.py +473 -0
  131. astrbot/core/platform/sources/discord/discord_platform_event.py +313 -0
  132. astrbot/core/platform/sources/lark/lark_adapter.py +27 -18
  133. astrbot/core/platform/sources/lark/lark_event.py +39 -13
  134. astrbot/core/platform/sources/misskey/misskey_adapter.py +770 -0
  135. astrbot/core/platform/sources/misskey/misskey_api.py +964 -0
  136. astrbot/core/platform/sources/misskey/misskey_event.py +163 -0
  137. astrbot/core/platform/sources/misskey/misskey_utils.py +550 -0
  138. astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +149 -33
  139. astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
  140. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
  141. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
  142. astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +14 -8
  143. astrbot/core/platform/sources/satori/satori_adapter.py +792 -0
  144. astrbot/core/platform/sources/satori/satori_event.py +432 -0
  145. astrbot/core/platform/sources/slack/client.py +164 -0
  146. astrbot/core/platform/sources/slack/slack_adapter.py +416 -0
  147. astrbot/core/platform/sources/slack/slack_event.py +253 -0
  148. astrbot/core/platform/sources/telegram/tg_adapter.py +100 -43
  149. astrbot/core/platform/sources/telegram/tg_event.py +136 -36
  150. astrbot/core/platform/sources/webchat/webchat_adapter.py +72 -22
  151. astrbot/core/platform/sources/webchat/webchat_event.py +46 -22
  152. astrbot/core/platform/sources/webchat/webchat_queue_mgr.py +35 -0
  153. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +926 -0
  154. astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +178 -0
  155. astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +159 -0
  156. astrbot/core/platform/sources/wecom/wecom_adapter.py +169 -27
  157. astrbot/core/platform/sources/wecom/wecom_event.py +162 -77
  158. astrbot/core/platform/sources/wecom/wecom_kf.py +279 -0
  159. astrbot/core/platform/sources/wecom/wecom_kf_message.py +196 -0
  160. astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +297 -0
  161. astrbot/core/platform/sources/wecom_ai_bot/__init__.py +15 -0
  162. astrbot/core/platform/sources/wecom_ai_bot/ierror.py +19 -0
  163. astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +472 -0
  164. astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +417 -0
  165. astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +152 -0
  166. astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +153 -0
  167. astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +168 -0
  168. astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +209 -0
  169. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +306 -0
  170. astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +186 -0
  171. astrbot/core/platform_message_history_mgr.py +49 -0
  172. astrbot/core/provider/__init__.py +2 -3
  173. astrbot/core/provider/entites.py +8 -8
  174. astrbot/core/provider/entities.py +154 -98
  175. astrbot/core/provider/func_tool_manager.py +446 -458
  176. astrbot/core/provider/manager.py +345 -207
  177. astrbot/core/provider/provider.py +188 -73
  178. astrbot/core/provider/register.py +9 -7
  179. astrbot/core/provider/sources/anthropic_source.py +295 -115
  180. astrbot/core/provider/sources/azure_tts_source.py +224 -0
  181. astrbot/core/provider/sources/bailian_rerank_source.py +236 -0
  182. astrbot/core/provider/sources/dashscope_tts.py +138 -14
  183. astrbot/core/provider/sources/edge_tts_source.py +24 -19
  184. astrbot/core/provider/sources/fishaudio_tts_api_source.py +58 -13
  185. astrbot/core/provider/sources/gemini_embedding_source.py +61 -0
  186. astrbot/core/provider/sources/gemini_source.py +310 -132
  187. astrbot/core/provider/sources/gemini_tts_source.py +81 -0
  188. astrbot/core/provider/sources/groq_source.py +15 -0
  189. astrbot/core/provider/sources/gsv_selfhosted_source.py +151 -0
  190. astrbot/core/provider/sources/gsvi_tts_source.py +14 -7
  191. astrbot/core/provider/sources/minimax_tts_api_source.py +159 -0
  192. astrbot/core/provider/sources/openai_embedding_source.py +40 -0
  193. astrbot/core/provider/sources/openai_source.py +241 -145
  194. astrbot/core/provider/sources/openai_tts_api_source.py +18 -7
  195. astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
  196. astrbot/core/provider/sources/vllm_rerank_source.py +71 -0
  197. astrbot/core/provider/sources/volcengine_tts.py +115 -0
  198. astrbot/core/provider/sources/whisper_api_source.py +18 -13
  199. astrbot/core/provider/sources/whisper_selfhosted_source.py +19 -12
  200. astrbot/core/provider/sources/xinference_rerank_source.py +116 -0
  201. astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
  202. astrbot/core/provider/sources/zhipu_source.py +6 -73
  203. astrbot/core/star/__init__.py +43 -11
  204. astrbot/core/star/config.py +17 -18
  205. astrbot/core/star/context.py +362 -138
  206. astrbot/core/star/filter/__init__.py +4 -3
  207. astrbot/core/star/filter/command.py +111 -35
  208. astrbot/core/star/filter/command_group.py +46 -34
  209. astrbot/core/star/filter/custom_filter.py +6 -5
  210. astrbot/core/star/filter/event_message_type.py +4 -2
  211. astrbot/core/star/filter/permission.py +4 -2
  212. astrbot/core/star/filter/platform_adapter_type.py +45 -12
  213. astrbot/core/star/filter/regex.py +4 -2
  214. astrbot/core/star/register/__init__.py +19 -15
  215. astrbot/core/star/register/star.py +41 -13
  216. astrbot/core/star/register/star_handler.py +236 -86
  217. astrbot/core/star/session_llm_manager.py +280 -0
  218. astrbot/core/star/session_plugin_manager.py +170 -0
  219. astrbot/core/star/star.py +36 -43
  220. astrbot/core/star/star_handler.py +47 -85
  221. astrbot/core/star/star_manager.py +442 -260
  222. astrbot/core/star/star_tools.py +167 -45
  223. astrbot/core/star/updator.py +17 -20
  224. astrbot/core/umop_config_router.py +106 -0
  225. astrbot/core/updator.py +38 -13
  226. astrbot/core/utils/astrbot_path.py +39 -0
  227. astrbot/core/utils/command_parser.py +1 -1
  228. astrbot/core/utils/io.py +119 -60
  229. astrbot/core/utils/log_pipe.py +1 -1
  230. astrbot/core/utils/metrics.py +11 -10
  231. astrbot/core/utils/migra_helper.py +73 -0
  232. astrbot/core/utils/path_util.py +63 -62
  233. astrbot/core/utils/pip_installer.py +37 -15
  234. astrbot/core/utils/session_lock.py +29 -0
  235. astrbot/core/utils/session_waiter.py +19 -20
  236. astrbot/core/utils/shared_preferences.py +174 -34
  237. astrbot/core/utils/t2i/__init__.py +4 -1
  238. astrbot/core/utils/t2i/local_strategy.py +386 -238
  239. astrbot/core/utils/t2i/network_strategy.py +109 -49
  240. astrbot/core/utils/t2i/renderer.py +29 -14
  241. astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
  242. astrbot/core/utils/t2i/template_manager.py +111 -0
  243. astrbot/core/utils/tencent_record_helper.py +115 -1
  244. astrbot/core/utils/version_comparator.py +10 -13
  245. astrbot/core/zip_updator.py +112 -65
  246. astrbot/dashboard/routes/__init__.py +20 -13
  247. astrbot/dashboard/routes/auth.py +20 -9
  248. astrbot/dashboard/routes/chat.py +297 -141
  249. astrbot/dashboard/routes/config.py +652 -55
  250. astrbot/dashboard/routes/conversation.py +107 -37
  251. astrbot/dashboard/routes/file.py +26 -0
  252. astrbot/dashboard/routes/knowledge_base.py +1244 -0
  253. astrbot/dashboard/routes/log.py +27 -2
  254. astrbot/dashboard/routes/persona.py +202 -0
  255. astrbot/dashboard/routes/plugin.py +197 -139
  256. astrbot/dashboard/routes/route.py +27 -7
  257. astrbot/dashboard/routes/session_management.py +354 -0
  258. astrbot/dashboard/routes/stat.py +85 -18
  259. astrbot/dashboard/routes/static_file.py +5 -2
  260. astrbot/dashboard/routes/t2i.py +233 -0
  261. astrbot/dashboard/routes/tools.py +184 -120
  262. astrbot/dashboard/routes/update.py +59 -36
  263. astrbot/dashboard/server.py +96 -36
  264. astrbot/dashboard/utils.py +165 -0
  265. astrbot-4.7.0.dist-info/METADATA +294 -0
  266. astrbot-4.7.0.dist-info/RECORD +274 -0
  267. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/WHEEL +1 -1
  268. astrbot/core/db/plugin/sqlite_impl.py +0 -112
  269. astrbot/core/db/sqlite_init.sql +0 -50
  270. astrbot/core/pipeline/platform_compatibility/stage.py +0 -56
  271. astrbot/core/pipeline/process_stage/method/llm_request.py +0 -606
  272. astrbot/core/platform/sources/gewechat/client.py +0 -806
  273. astrbot/core/platform/sources/gewechat/downloader.py +0 -55
  274. astrbot/core/platform/sources/gewechat/gewechat_event.py +0 -255
  275. astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py +0 -103
  276. astrbot/core/platform/sources/gewechat/xml_data_parser.py +0 -110
  277. astrbot/core/provider/sources/dashscope_source.py +0 -203
  278. astrbot/core/provider/sources/dify_source.py +0 -281
  279. astrbot/core/provider/sources/llmtuner_source.py +0 -132
  280. astrbot/core/rag/embedding/openai_source.py +0 -20
  281. astrbot/core/rag/knowledge_db_mgr.py +0 -94
  282. astrbot/core/rag/store/__init__.py +0 -9
  283. astrbot/core/rag/store/chroma_db.py +0 -42
  284. astrbot/core/utils/dify_api_client.py +0 -152
  285. astrbot-3.5.6.dist-info/METADATA +0 -249
  286. astrbot-3.5.6.dist-info/RECORD +0 -158
  287. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/entry_points.txt +0 -0
  288. {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/licenses/LICENSE +0 -0
@@ -0,0 +1,354 @@
1
+ from quart import request
2
+ from sqlalchemy.ext.asyncio import AsyncSession
3
+ from sqlmodel import col, select
4
+
5
+ from astrbot.core import logger, sp
6
+ from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
7
+ from astrbot.core.db import BaseDatabase
8
+ from astrbot.core.db.po import ConversationV2, Preference
9
+ from astrbot.core.provider.entities import ProviderType
10
+
11
+ from .route import Response, Route, RouteContext
12
+
13
+ AVAILABLE_SESSION_RULE_KEYS = [
14
+ "session_service_config",
15
+ "session_plugin_config",
16
+ "kb_config",
17
+ f"provider_perf_{ProviderType.CHAT_COMPLETION.value}",
18
+ f"provider_perf_{ProviderType.SPEECH_TO_TEXT.value}",
19
+ f"provider_perf_{ProviderType.TEXT_TO_SPEECH.value}",
20
+ ]
21
+
22
+
23
+ class SessionManagementRoute(Route):
24
+ def __init__(
25
+ self,
26
+ context: RouteContext,
27
+ db_helper: BaseDatabase,
28
+ core_lifecycle: AstrBotCoreLifecycle,
29
+ ) -> None:
30
+ super().__init__(context)
31
+ self.db_helper = db_helper
32
+ self.routes = {
33
+ "/session/list-rule": ("GET", self.list_session_rule),
34
+ "/session/update-rule": ("POST", self.update_session_rule),
35
+ "/session/delete-rule": ("POST", self.delete_session_rule),
36
+ "/session/batch-delete-rule": ("POST", self.batch_delete_session_rule),
37
+ "/session/active-umos": ("GET", self.list_umos),
38
+ }
39
+ self.conv_mgr = core_lifecycle.conversation_manager
40
+ self.core_lifecycle = core_lifecycle
41
+ self.register_routes()
42
+
43
+ async def _get_umo_rules(
44
+ self, page: int = 1, page_size: int = 10, search: str = ""
45
+ ) -> tuple[dict, int]:
46
+ """获取所有带有自定义规则的 umo 及其规则内容(支持分页和搜索)。
47
+
48
+ 如果某个 umo 在 preference 中有以下字段,则表示有自定义规则:
49
+
50
+ 1. session_service_config (包含了 是否启用这个umo, 这个umo是否启用 llm, 这个umo是否启用tts, umo自定义名称。)
51
+ 2. session_plugin_config (包含了 这个 umo 的 plugin set)
52
+ 3. provider_perf_{ProviderType.value} (包含了这个 umo 所选择使用的 provider 信息)
53
+ 4. kb_config (包含了这个 umo 的知识库相关配置)
54
+
55
+ Args:
56
+ page: 页码,从 1 开始
57
+ page_size: 每页数量
58
+ search: 搜索关键词,匹配 umo 或 custom_name
59
+
60
+ Returns:
61
+ tuple[dict, int]: (umo_rules, total) - 分页后的 umo 规则和总数
62
+ """
63
+ umo_rules = {}
64
+ async with self.db_helper.get_db() as session:
65
+ session: AsyncSession
66
+ result = await session.execute(
67
+ select(Preference).where(
68
+ col(Preference.scope) == "umo",
69
+ col(Preference.key).in_(AVAILABLE_SESSION_RULE_KEYS),
70
+ )
71
+ )
72
+ prefs = result.scalars().all()
73
+ for pref in prefs:
74
+ umo_id = pref.scope_id
75
+ if umo_id not in umo_rules:
76
+ umo_rules[umo_id] = {}
77
+ umo_rules[umo_id][pref.key] = pref.value["val"]
78
+
79
+ # 搜索过滤
80
+ if search:
81
+ search_lower = search.lower()
82
+ filtered_rules = {}
83
+ for umo_id, rules in umo_rules.items():
84
+ # 匹配 umo
85
+ if search_lower in umo_id.lower():
86
+ filtered_rules[umo_id] = rules
87
+ continue
88
+ # 匹配 custom_name
89
+ svc_config = rules.get("session_service_config", {})
90
+ custom_name = svc_config.get("custom_name", "") if svc_config else ""
91
+ if custom_name and search_lower in custom_name.lower():
92
+ filtered_rules[umo_id] = rules
93
+ umo_rules = filtered_rules
94
+
95
+ # 获取总数
96
+ total = len(umo_rules)
97
+
98
+ # 分页处理
99
+ all_umo_ids = list(umo_rules.keys())
100
+ start_idx = (page - 1) * page_size
101
+ end_idx = start_idx + page_size
102
+ paginated_umo_ids = all_umo_ids[start_idx:end_idx]
103
+
104
+ # 只返回分页后的数据
105
+ paginated_rules = {umo_id: umo_rules[umo_id] for umo_id in paginated_umo_ids}
106
+
107
+ return paginated_rules, total
108
+
109
+ async def list_session_rule(self):
110
+ """获取所有自定义的规则(支持分页和搜索)
111
+
112
+ 返回已配置规则的 umo 列表及其规则内容,以及可用的 personas 和 providers
113
+
114
+ Query 参数:
115
+ page: 页码,默认为 1
116
+ page_size: 每页数量,默认为 10
117
+ search: 搜索关键词,匹配 umo 或 custom_name
118
+ """
119
+ try:
120
+ # 获取分页和搜索参数
121
+ page = request.args.get("page", 1, type=int)
122
+ page_size = request.args.get("page_size", 10, type=int)
123
+ search = request.args.get("search", "", type=str).strip()
124
+
125
+ # 参数校验
126
+ if page < 1:
127
+ page = 1
128
+ if page_size < 1:
129
+ page_size = 10
130
+ if page_size > 100:
131
+ page_size = 100
132
+
133
+ umo_rules, total = await self._get_umo_rules(
134
+ page=page, page_size=page_size, search=search
135
+ )
136
+
137
+ # 构建规则列表
138
+ rules_list = []
139
+ for umo, rules in umo_rules.items():
140
+ rule_info = {
141
+ "umo": umo,
142
+ "rules": rules,
143
+ }
144
+ # 解析 umo 格式: 平台:消息类型:会话ID
145
+ parts = umo.split(":")
146
+ if len(parts) >= 3:
147
+ rule_info["platform"] = parts[0]
148
+ rule_info["message_type"] = parts[1]
149
+ rule_info["session_id"] = parts[2]
150
+ rules_list.append(rule_info)
151
+
152
+ # 获取可用的 providers 和 personas
153
+ provider_manager = self.core_lifecycle.provider_manager
154
+ persona_mgr = self.core_lifecycle.persona_mgr
155
+
156
+ available_personas = [
157
+ {"name": p["name"], "prompt": p.get("prompt", "")}
158
+ for p in persona_mgr.personas_v3
159
+ ]
160
+
161
+ available_chat_providers = [
162
+ {
163
+ "id": p.meta().id,
164
+ "name": p.meta().id,
165
+ "model": p.meta().model,
166
+ }
167
+ for p in provider_manager.provider_insts
168
+ ]
169
+
170
+ available_stt_providers = [
171
+ {
172
+ "id": p.meta().id,
173
+ "name": p.meta().id,
174
+ "model": p.meta().model,
175
+ }
176
+ for p in provider_manager.stt_provider_insts
177
+ ]
178
+
179
+ available_tts_providers = [
180
+ {
181
+ "id": p.meta().id,
182
+ "name": p.meta().id,
183
+ "model": p.meta().model,
184
+ }
185
+ for p in provider_manager.tts_provider_insts
186
+ ]
187
+
188
+ return (
189
+ Response()
190
+ .ok(
191
+ {
192
+ "rules": rules_list,
193
+ "total": total,
194
+ "page": page,
195
+ "page_size": page_size,
196
+ "available_personas": available_personas,
197
+ "available_chat_providers": available_chat_providers,
198
+ "available_stt_providers": available_stt_providers,
199
+ "available_tts_providers": available_tts_providers,
200
+ "available_rule_keys": AVAILABLE_SESSION_RULE_KEYS,
201
+ }
202
+ )
203
+ .__dict__
204
+ )
205
+ except Exception as e:
206
+ logger.error(f"获取规则列表失败: {e!s}")
207
+ return Response().error(f"获取规则列表失败: {e!s}").__dict__
208
+
209
+ async def update_session_rule(self):
210
+ """更新某个 umo 的自定义规则
211
+
212
+ 请求体:
213
+ {
214
+ "umo": "平台:消息类型:会话ID",
215
+ "rule_key": "session_service_config" | "session_plugin_config" | "kb_config" | "provider_perf_xxx",
216
+ "rule_value": {...} // 规则值,具体结构根据 rule_key 不同而不同
217
+ }
218
+ """
219
+ try:
220
+ data = await request.get_json()
221
+ umo = data.get("umo")
222
+ rule_key = data.get("rule_key")
223
+ rule_value = data.get("rule_value")
224
+
225
+ if not umo:
226
+ return Response().error("缺少必要参数: umo").__dict__
227
+ if not rule_key:
228
+ return Response().error("缺少必要参数: rule_key").__dict__
229
+ if rule_key not in AVAILABLE_SESSION_RULE_KEYS:
230
+ return Response().error(f"不支持的规则键: {rule_key}").__dict__
231
+
232
+ # 使用 shared preferences 更新规则
233
+ await sp.session_put(umo, rule_key, rule_value)
234
+
235
+ return (
236
+ Response()
237
+ .ok({"message": f"规则 {rule_key} 已更新", "umo": umo})
238
+ .__dict__
239
+ )
240
+ except Exception as e:
241
+ logger.error(f"更新会话规则失败: {e!s}")
242
+ return Response().error(f"更新会话规则失败: {e!s}").__dict__
243
+
244
+ async def delete_session_rule(self):
245
+ """删除某个 umo 的自定义规则
246
+
247
+ 请求体:
248
+ {
249
+ "umo": "平台:消息类型:会话ID",
250
+ "rule_key": "session_service_config" | "session_plugin_config" | ... (可选,不传则删除所有规则)
251
+ }
252
+ """
253
+ try:
254
+ data = await request.get_json()
255
+ umo = data.get("umo")
256
+ rule_key = data.get("rule_key")
257
+
258
+ if not umo:
259
+ return Response().error("缺少必要参数: umo").__dict__
260
+
261
+ if rule_key:
262
+ # 删除单个规则
263
+ if rule_key not in AVAILABLE_SESSION_RULE_KEYS:
264
+ return Response().error(f"不支持的规则键: {rule_key}").__dict__
265
+ await sp.session_remove(umo, rule_key)
266
+ return (
267
+ Response()
268
+ .ok({"message": f"规则 {rule_key} 已删除", "umo": umo})
269
+ .__dict__
270
+ )
271
+ else:
272
+ # 删除该 umo 的所有规则
273
+ await sp.clear_async("umo", umo)
274
+ return Response().ok({"message": "所有规则已删除", "umo": umo}).__dict__
275
+ except Exception as e:
276
+ logger.error(f"删除会话规则失败: {e!s}")
277
+ return Response().error(f"删除会话规则失败: {e!s}").__dict__
278
+
279
+ async def batch_delete_session_rule(self):
280
+ """批量删除多个 umo 的自定义规则
281
+
282
+ 请求体:
283
+ {
284
+ "umos": ["平台:消息类型:会话ID", ...] // umo 列表
285
+ }
286
+ """
287
+ try:
288
+ data = await request.get_json()
289
+ umos = data.get("umos", [])
290
+
291
+ if not umos:
292
+ return Response().error("缺少必要参数: umos").__dict__
293
+
294
+ if not isinstance(umos, list):
295
+ return Response().error("参数 umos 必须是数组").__dict__
296
+
297
+ # 批量删除
298
+ deleted_count = 0
299
+ failed_umos = []
300
+ for umo in umos:
301
+ try:
302
+ await sp.clear_async("umo", umo)
303
+ deleted_count += 1
304
+ except Exception as e:
305
+ logger.error(f"删除 umo {umo} 的规则失败: {e!s}")
306
+ failed_umos.append(umo)
307
+
308
+ if failed_umos:
309
+ return (
310
+ Response()
311
+ .ok(
312
+ {
313
+ "message": f"已删除 {deleted_count} 条规则,{len(failed_umos)} 条删除失败",
314
+ "deleted_count": deleted_count,
315
+ "failed_umos": failed_umos,
316
+ }
317
+ )
318
+ .__dict__
319
+ )
320
+ else:
321
+ return (
322
+ Response()
323
+ .ok(
324
+ {
325
+ "message": f"已删除 {deleted_count} 条规则",
326
+ "deleted_count": deleted_count,
327
+ }
328
+ )
329
+ .__dict__
330
+ )
331
+ except Exception as e:
332
+ logger.error(f"批量删除会话规则失败: {e!s}")
333
+ return Response().error(f"批量删除会话规则失败: {e!s}").__dict__
334
+
335
+ async def list_umos(self):
336
+ """列出所有有对话记录的 umo,从 Conversations 表中找
337
+
338
+ 仅返回 umo 字符串列表,用于用户在创建规则时选择 umo
339
+ """
340
+ try:
341
+ # 从 Conversation 表获取所有 distinct user_id (即 umo)
342
+ async with self.db_helper.get_db() as session:
343
+ session: AsyncSession
344
+ result = await session.execute(
345
+ select(ConversationV2.user_id)
346
+ .distinct()
347
+ .order_by(ConversationV2.user_id)
348
+ )
349
+ umos = [row[0] for row in result.fetchall()]
350
+
351
+ return Response().ok({"umos": umos}).__dict__
352
+ except Exception as e:
353
+ logger.error(f"获取 UMO 列表失败: {e!s}")
354
+ return Response().error(f"获取 UMO 列表失败: {e!s}").__dict__
@@ -1,14 +1,19 @@
1
+ import threading
2
+ import time
1
3
  import traceback
4
+
5
+ import aiohttp
2
6
  import psutil
3
- import time
4
- import threading
5
- from .route import Route, Response, RouteContext
6
- from astrbot.core import logger
7
7
  from quart import request
8
+
9
+ from astrbot.core import DEMO_MODE, logger
10
+ from astrbot.core.config import VERSION
8
11
  from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
9
12
  from astrbot.core.db import BaseDatabase
10
- from astrbot.core.config import VERSION
11
- from astrbot.core import DEMO_MODE
13
+ from astrbot.core.db.migration.helper import check_migration_needed_v4
14
+ from astrbot.core.utils.io import get_dashboard_version
15
+
16
+ from .route import Response, Route, RouteContext
12
17
 
13
18
 
14
19
  class StatRoute(Route):
@@ -24,6 +29,7 @@ class StatRoute(Route):
24
29
  "/stat/version": ("GET", self.get_version),
25
30
  "/stat/start-time": ("GET", self.get_start_time),
26
31
  "/stat/restart-core": ("POST", self.restart_core),
32
+ "/stat/test-ghproxy-connection": ("POST", self.test_ghproxy_connection),
27
33
  }
28
34
  self.db_helper = db_helper
29
35
  self.register_routes()
@@ -40,13 +46,36 @@ class StatRoute(Route):
40
46
  await self.core_lifecycle.restart()
41
47
  return Response().ok().__dict__
42
48
 
43
- def format_sec(self, sec: int):
44
- m, s = divmod(sec, 60)
45
- h, m = divmod(m, 60)
46
- return f"{h}小时{m}分{s}秒"
49
+ def _get_running_time_components(self, total_seconds: int):
50
+ """将总秒数转换为时分秒组件"""
51
+ minutes, seconds = divmod(total_seconds, 60)
52
+ hours, minutes = divmod(minutes, 60)
53
+ return {"hours": hours, "minutes": minutes, "seconds": seconds}
54
+
55
+ def is_default_cred(self):
56
+ username = self.config["dashboard"]["username"]
57
+ password = self.config["dashboard"]["password"]
58
+ return (
59
+ username == "astrbot"
60
+ and password == "77b90590a8945a7d36c963981a307dc9"
61
+ and not DEMO_MODE
62
+ )
47
63
 
48
64
  async def get_version(self):
49
- return Response().ok({"version": VERSION}).__dict__
65
+ need_migration = await check_migration_needed_v4(self.core_lifecycle.db)
66
+
67
+ return (
68
+ Response()
69
+ .ok(
70
+ {
71
+ "version": VERSION,
72
+ "dashboard_version": await get_dashboard_version(),
73
+ "change_pwd_hint": self.is_default_cred(),
74
+ "need_migration": need_migration,
75
+ },
76
+ )
77
+ .__dict__
78
+ )
50
79
 
51
80
  async def get_start_time(self):
52
81
  return Response().ok({"start_time": self.core_lifecycle.start_time}).__dict__
@@ -61,7 +90,7 @@ class StatRoute(Route):
61
90
  message_time_based_stats = []
62
91
 
63
92
  idx = 0
64
- for bucket_end in range(start_time, now, 1800):
93
+ for bucket_end in range(start_time, now, 3600):
65
94
  cnt = 0
66
95
  while (
67
96
  idx < len(stat.platform)
@@ -87,21 +116,24 @@ class StatRoute(Route):
87
116
  }
88
117
  plugin_info.append(info)
89
118
 
119
+ # 计算运行时长组件
120
+ running_time = self._get_running_time_components(
121
+ int(time.time()) - self.core_lifecycle.start_time,
122
+ )
123
+
90
124
  stat_dict.update(
91
125
  {
92
126
  "platform": self.db_helper.get_grouped_base_stats(
93
- offset_sec
127
+ offset_sec,
94
128
  ).platform,
95
129
  "message_count": self.db_helper.get_total_message_count() or 0,
96
130
  "platform_count": len(
97
- self.core_lifecycle.platform_manager.get_insts()
131
+ self.core_lifecycle.platform_manager.get_insts(),
98
132
  ),
99
133
  "plugin_count": len(plugins),
100
134
  "plugins": plugin_info,
101
135
  "message_time_series": message_time_based_stats,
102
- "running": self.format_sec(
103
- int(time.time()) - self.core_lifecycle.start_time
104
- ),
136
+ "running": running_time, # 现在返回时间组件而不是格式化的字符串
105
137
  "memory": {
106
138
  "process": psutil.Process().memory_info().rss >> 20,
107
139
  "system": psutil.virtual_memory().total >> 20,
@@ -109,10 +141,45 @@ class StatRoute(Route):
109
141
  "cpu_percent": round(cpu_percent, 1),
110
142
  "thread_count": thread_count,
111
143
  "start_time": self.core_lifecycle.start_time,
112
- }
144
+ },
113
145
  )
114
146
 
115
147
  return Response().ok(stat_dict).__dict__
116
148
  except Exception as e:
117
149
  logger.error(traceback.format_exc())
118
150
  return Response().error(e.__str__()).__dict__
151
+
152
+ async def test_ghproxy_connection(self):
153
+ """测试 GitHub 代理连接是否可用。"""
154
+ try:
155
+ data = await request.get_json()
156
+ proxy_url: str = data.get("proxy_url")
157
+
158
+ if not proxy_url:
159
+ return Response().error("proxy_url is required").__dict__
160
+
161
+ proxy_url = proxy_url.rstrip("/")
162
+
163
+ test_url = f"{proxy_url}/https://github.com/AstrBotDevs/AstrBot/raw/refs/heads/master/.python-version"
164
+ start_time = time.time()
165
+
166
+ async with (
167
+ aiohttp.ClientSession() as session,
168
+ session.get(
169
+ test_url,
170
+ timeout=aiohttp.ClientTimeout(total=10),
171
+ ) as response,
172
+ ):
173
+ if response.status == 200:
174
+ end_time = time.time()
175
+ _ = await response.text()
176
+ ret = {
177
+ "latency": round((end_time - start_time) * 1000, 2),
178
+ }
179
+ return Response().ok(data=ret).__dict__
180
+ return (
181
+ Response().error(f"Failed. Status code: {response.status}").__dict__
182
+ )
183
+ except Exception as e:
184
+ logger.error(traceback.format_exc())
185
+ return Response().error(f"Error: {e!s}").__dict__
@@ -12,7 +12,10 @@ class StaticFileRoute(Route):
12
12
  "/logs",
13
13
  "/extension",
14
14
  "/dashboard/default",
15
- "/project-atri",
15
+ "/alkaid",
16
+ "/alkaid/knowledge-base",
17
+ "/alkaid/long-term-memory",
18
+ "/alkaid/other",
16
19
  "/console",
17
20
  "/chat",
18
21
  "/settings",
@@ -28,7 +31,7 @@ class StaticFileRoute(Route):
28
31
 
29
32
  @self.app.errorhandler(404)
30
33
  async def page_not_found(e):
31
- return "404 Not found。如果你初次使用打开面板发现 404, 请参考文档: https://astrbot.app/faq.html"
34
+ return "404 Not found。如果你初次使用打开面板发现 404, 请参考文档: https://astrbot.app/faq.html。如果你正在测试回调地址可达性,显示这段文字说明测试成功了。"
32
35
 
33
36
  async def index(self):
34
37
  return await self.app.send_static_file("index.html")