AstrBot 4.5.1__py3-none-any.whl → 4.5.2__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.
- astrbot/api/__init__.py +10 -11
- astrbot/api/event/__init__.py +5 -6
- astrbot/api/event/filter/__init__.py +37 -36
- astrbot/api/platform/__init__.py +7 -8
- astrbot/api/provider/__init__.py +7 -7
- astrbot/api/star/__init__.py +3 -4
- astrbot/api/util/__init__.py +2 -2
- astrbot/cli/__main__.py +5 -5
- astrbot/cli/commands/__init__.py +3 -3
- astrbot/cli/commands/cmd_conf.py +19 -16
- astrbot/cli/commands/cmd_init.py +3 -2
- astrbot/cli/commands/cmd_plug.py +8 -10
- astrbot/cli/commands/cmd_run.py +5 -6
- astrbot/cli/utils/__init__.py +6 -6
- astrbot/cli/utils/basic.py +14 -14
- astrbot/cli/utils/plugin.py +24 -15
- astrbot/cli/utils/version_comparator.py +10 -12
- astrbot/core/__init__.py +8 -6
- astrbot/core/agent/agent.py +3 -2
- astrbot/core/agent/handoff.py +6 -2
- astrbot/core/agent/hooks.py +9 -6
- astrbot/core/agent/mcp_client.py +50 -15
- astrbot/core/agent/message.py +168 -0
- astrbot/core/agent/response.py +2 -1
- astrbot/core/agent/run_context.py +2 -3
- astrbot/core/agent/runners/base.py +10 -13
- astrbot/core/agent/runners/tool_loop_agent_runner.py +52 -51
- astrbot/core/agent/tool.py +60 -41
- astrbot/core/agent/tool_executor.py +9 -3
- astrbot/core/astr_agent_context.py +3 -1
- astrbot/core/astrbot_config_mgr.py +29 -9
- astrbot/core/config/__init__.py +2 -2
- astrbot/core/config/astrbot_config.py +28 -26
- astrbot/core/config/default.py +4 -6
- astrbot/core/conversation_mgr.py +105 -36
- astrbot/core/core_lifecycle.py +68 -54
- astrbot/core/db/__init__.py +33 -18
- astrbot/core/db/migration/helper.py +12 -10
- astrbot/core/db/migration/migra_3_to_4.py +53 -34
- astrbot/core/db/migration/migra_45_to_46.py +1 -1
- astrbot/core/db/migration/shared_preferences_v3.py +2 -1
- astrbot/core/db/migration/sqlite_v3.py +26 -23
- astrbot/core/db/po.py +27 -18
- astrbot/core/db/sqlite.py +74 -45
- astrbot/core/db/vec_db/base.py +10 -14
- astrbot/core/db/vec_db/faiss_impl/document_storage.py +90 -77
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +9 -3
- astrbot/core/db/vec_db/faiss_impl/vec_db.py +36 -31
- astrbot/core/event_bus.py +8 -6
- astrbot/core/file_token_service.py +6 -5
- astrbot/core/initial_loader.py +7 -5
- astrbot/core/knowledge_base/chunking/__init__.py +1 -3
- astrbot/core/knowledge_base/chunking/base.py +1 -0
- astrbot/core/knowledge_base/chunking/fixed_size.py +2 -0
- astrbot/core/knowledge_base/chunking/recursive.py +16 -10
- astrbot/core/knowledge_base/kb_db_sqlite.py +50 -48
- astrbot/core/knowledge_base/kb_helper.py +30 -17
- astrbot/core/knowledge_base/kb_mgr.py +6 -7
- astrbot/core/knowledge_base/models.py +10 -4
- astrbot/core/knowledge_base/parsers/__init__.py +3 -5
- astrbot/core/knowledge_base/parsers/base.py +1 -0
- astrbot/core/knowledge_base/parsers/markitdown_parser.py +2 -1
- astrbot/core/knowledge_base/parsers/pdf_parser.py +2 -1
- astrbot/core/knowledge_base/parsers/text_parser.py +1 -0
- astrbot/core/knowledge_base/parsers/util.py +1 -1
- astrbot/core/knowledge_base/retrieval/__init__.py +6 -8
- astrbot/core/knowledge_base/retrieval/manager.py +17 -14
- astrbot/core/knowledge_base/retrieval/rank_fusion.py +7 -3
- astrbot/core/knowledge_base/retrieval/sparse_retriever.py +11 -5
- astrbot/core/log.py +21 -13
- astrbot/core/message/components.py +123 -217
- astrbot/core/message/message_event_result.py +24 -24
- astrbot/core/persona_mgr.py +20 -11
- astrbot/core/pipeline/__init__.py +7 -7
- astrbot/core/pipeline/content_safety_check/stage.py +13 -9
- astrbot/core/pipeline/content_safety_check/strategies/__init__.py +1 -2
- astrbot/core/pipeline/content_safety_check/strategies/baidu_aip.py +12 -13
- astrbot/core/pipeline/content_safety_check/strategies/keywords.py +1 -0
- astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
- astrbot/core/pipeline/context.py +4 -1
- astrbot/core/pipeline/context_utils.py +77 -7
- astrbot/core/pipeline/preprocess_stage/stage.py +12 -9
- astrbot/core/pipeline/process_stage/method/llm_request.py +125 -72
- astrbot/core/pipeline/process_stage/method/star_request.py +19 -17
- astrbot/core/pipeline/process_stage/stage.py +13 -10
- astrbot/core/pipeline/process_stage/utils.py +6 -5
- astrbot/core/pipeline/rate_limit_check/stage.py +37 -36
- astrbot/core/pipeline/respond/stage.py +23 -20
- astrbot/core/pipeline/result_decorate/stage.py +31 -23
- astrbot/core/pipeline/scheduler.py +12 -8
- astrbot/core/pipeline/session_status_check/stage.py +12 -8
- astrbot/core/pipeline/stage.py +10 -4
- astrbot/core/pipeline/waking_check/stage.py +24 -18
- astrbot/core/pipeline/whitelist_check/stage.py +10 -7
- astrbot/core/platform/__init__.py +6 -6
- astrbot/core/platform/astr_message_event.py +76 -110
- astrbot/core/platform/astrbot_message.py +11 -13
- astrbot/core/platform/manager.py +16 -15
- astrbot/core/platform/message_session.py +5 -3
- astrbot/core/platform/platform.py +16 -24
- astrbot/core/platform/platform_metadata.py +4 -4
- astrbot/core/platform/register.py +8 -8
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +23 -15
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +51 -33
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +42 -27
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +7 -3
- astrbot/core/platform/sources/discord/client.py +9 -6
- astrbot/core/platform/sources/discord/components.py +18 -14
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +45 -30
- astrbot/core/platform/sources/discord/discord_platform_event.py +38 -30
- astrbot/core/platform/sources/lark/lark_adapter.py +23 -17
- astrbot/core/platform/sources/lark/lark_event.py +21 -14
- astrbot/core/platform/sources/misskey/misskey_adapter.py +107 -67
- astrbot/core/platform/sources/misskey/misskey_api.py +153 -129
- astrbot/core/platform/sources/misskey/misskey_event.py +20 -15
- astrbot/core/platform/sources/misskey/misskey_utils.py +74 -62
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +63 -44
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +41 -26
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -17
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_event.py +3 -1
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +12 -7
- astrbot/core/platform/sources/satori/satori_adapter.py +56 -38
- astrbot/core/platform/sources/satori/satori_event.py +34 -25
- astrbot/core/platform/sources/slack/client.py +11 -9
- astrbot/core/platform/sources/slack/slack_adapter.py +52 -36
- astrbot/core/platform/sources/slack/slack_event.py +34 -24
- astrbot/core/platform/sources/telegram/tg_adapter.py +38 -18
- astrbot/core/platform/sources/telegram/tg_event.py +32 -18
- astrbot/core/platform/sources/webchat/webchat_adapter.py +27 -17
- astrbot/core/platform/sources/webchat/webchat_event.py +14 -10
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +115 -120
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +9 -8
- astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +15 -16
- astrbot/core/platform/sources/wecom/wecom_adapter.py +35 -18
- astrbot/core/platform/sources/wecom/wecom_event.py +55 -48
- astrbot/core/platform/sources/wecom/wecom_kf.py +34 -44
- astrbot/core/platform/sources/wecom/wecom_kf_message.py +26 -10
- astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +18 -10
- astrbot/core/platform/sources/wecom_ai_bot/__init__.py +3 -5
- astrbot/core/platform/sources/wecom_ai_bot/ierror.py +0 -1
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +61 -37
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +67 -28
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +8 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +18 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +14 -12
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +22 -12
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +40 -26
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +47 -45
- astrbot/core/platform_message_history_mgr.py +5 -3
- astrbot/core/provider/__init__.py +2 -3
- astrbot/core/provider/entites.py +8 -8
- astrbot/core/provider/entities.py +61 -75
- astrbot/core/provider/func_tool_manager.py +59 -55
- astrbot/core/provider/manager.py +32 -22
- astrbot/core/provider/provider.py +72 -46
- astrbot/core/provider/register.py +7 -7
- astrbot/core/provider/sources/anthropic_source.py +48 -30
- astrbot/core/provider/sources/azure_tts_source.py +17 -13
- astrbot/core/provider/sources/coze_api_client.py +27 -17
- astrbot/core/provider/sources/coze_source.py +104 -87
- astrbot/core/provider/sources/dashscope_source.py +18 -11
- astrbot/core/provider/sources/dashscope_tts.py +36 -23
- astrbot/core/provider/sources/dify_source.py +25 -20
- astrbot/core/provider/sources/edge_tts_source.py +21 -17
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +22 -14
- astrbot/core/provider/sources/gemini_embedding_source.py +12 -13
- astrbot/core/provider/sources/gemini_source.py +72 -58
- astrbot/core/provider/sources/gemini_tts_source.py +8 -6
- astrbot/core/provider/sources/gsv_selfhosted_source.py +17 -14
- astrbot/core/provider/sources/gsvi_tts_source.py +11 -7
- astrbot/core/provider/sources/minimax_tts_api_source.py +50 -40
- astrbot/core/provider/sources/openai_embedding_source.py +6 -8
- astrbot/core/provider/sources/openai_source.py +77 -69
- astrbot/core/provider/sources/openai_tts_api_source.py +14 -6
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
- astrbot/core/provider/sources/vllm_rerank_source.py +10 -4
- astrbot/core/provider/sources/volcengine_tts.py +38 -31
- astrbot/core/provider/sources/whisper_api_source.py +14 -12
- astrbot/core/provider/sources/whisper_selfhosted_source.py +15 -11
- astrbot/core/provider/sources/xinference_rerank_source.py +16 -8
- astrbot/core/provider/sources/xinference_stt_provider.py +35 -25
- astrbot/core/star/__init__.py +16 -11
- astrbot/core/star/config.py +10 -15
- astrbot/core/star/context.py +97 -75
- astrbot/core/star/filter/__init__.py +4 -3
- astrbot/core/star/filter/command.py +30 -28
- astrbot/core/star/filter/command_group.py +27 -24
- astrbot/core/star/filter/custom_filter.py +6 -5
- astrbot/core/star/filter/event_message_type.py +4 -2
- astrbot/core/star/filter/permission.py +4 -2
- astrbot/core/star/filter/platform_adapter_type.py +4 -2
- astrbot/core/star/filter/regex.py +4 -2
- astrbot/core/star/register/__init__.py +19 -19
- astrbot/core/star/register/star.py +6 -2
- astrbot/core/star/register/star_handler.py +96 -73
- astrbot/core/star/session_llm_manager.py +48 -14
- astrbot/core/star/session_plugin_manager.py +29 -15
- astrbot/core/star/star.py +1 -2
- astrbot/core/star/star_handler.py +13 -8
- astrbot/core/star/star_manager.py +151 -59
- astrbot/core/star/star_tools.py +44 -37
- astrbot/core/star/updator.py +10 -10
- astrbot/core/umop_config_router.py +10 -4
- astrbot/core/updator.py +13 -5
- astrbot/core/utils/astrbot_path.py +3 -5
- astrbot/core/utils/dify_api_client.py +33 -15
- astrbot/core/utils/io.py +66 -42
- astrbot/core/utils/log_pipe.py +1 -1
- astrbot/core/utils/metrics.py +7 -7
- astrbot/core/utils/path_util.py +15 -16
- astrbot/core/utils/pip_installer.py +5 -5
- astrbot/core/utils/session_waiter.py +19 -20
- astrbot/core/utils/shared_preferences.py +45 -20
- astrbot/core/utils/t2i/__init__.py +4 -1
- astrbot/core/utils/t2i/network_strategy.py +35 -26
- astrbot/core/utils/t2i/renderer.py +11 -5
- astrbot/core/utils/t2i/template_manager.py +14 -15
- astrbot/core/utils/tencent_record_helper.py +19 -13
- astrbot/core/utils/version_comparator.py +10 -13
- astrbot/core/zip_updator.py +43 -40
- astrbot/dashboard/routes/__init__.py +18 -18
- astrbot/dashboard/routes/auth.py +10 -8
- astrbot/dashboard/routes/chat.py +30 -21
- astrbot/dashboard/routes/config.py +92 -75
- astrbot/dashboard/routes/conversation.py +46 -39
- astrbot/dashboard/routes/file.py +4 -2
- astrbot/dashboard/routes/knowledge_base.py +47 -40
- astrbot/dashboard/routes/log.py +9 -4
- astrbot/dashboard/routes/persona.py +19 -16
- astrbot/dashboard/routes/plugin.py +69 -55
- astrbot/dashboard/routes/route.py +3 -1
- astrbot/dashboard/routes/session_management.py +130 -116
- astrbot/dashboard/routes/stat.py +34 -34
- astrbot/dashboard/routes/t2i.py +15 -12
- astrbot/dashboard/routes/tools.py +47 -52
- astrbot/dashboard/routes/update.py +32 -28
- astrbot/dashboard/server.py +30 -26
- astrbot/dashboard/utils.py +8 -4
- {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/METADATA +2 -1
- astrbot-4.5.2.dist-info/RECORD +261 -0
- astrbot-4.5.1.dist-info/RECORD +0 -260
- {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/WHEEL +0 -0
- {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/entry_points.txt +0 -0
- {astrbot-4.5.1.dist-info → astrbot-4.5.2.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,14 +1,17 @@
|
|
|
1
|
-
import aiohttp
|
|
2
1
|
import asyncio
|
|
3
|
-
import ssl
|
|
4
|
-
import certifi
|
|
5
2
|
import logging
|
|
6
3
|
import random
|
|
7
|
-
|
|
4
|
+
import ssl
|
|
5
|
+
|
|
6
|
+
import aiohttp
|
|
7
|
+
import certifi
|
|
8
|
+
|
|
8
9
|
from astrbot.core.config import VERSION
|
|
9
10
|
from astrbot.core.utils.io import download_image_by_url
|
|
10
11
|
from astrbot.core.utils.t2i.template_manager import TemplateManager
|
|
11
12
|
|
|
13
|
+
from . import RenderStrategy
|
|
14
|
+
|
|
12
15
|
ASTRBOT_T2I_DEFAULT_ENDPOINT = "https://t2i.soulter.top/text2img"
|
|
13
16
|
|
|
14
17
|
logger = logging.getLogger("astrbot")
|
|
@@ -38,7 +41,7 @@ class NetworkRenderStrategy(RenderStrategy):
|
|
|
38
41
|
try:
|
|
39
42
|
async with aiohttp.ClientSession() as session:
|
|
40
43
|
async with session.get(
|
|
41
|
-
"https://api.soulter.top/astrbot/t2i-endpoints"
|
|
44
|
+
"https://api.soulter.top/astrbot/t2i-endpoints",
|
|
42
45
|
) as resp:
|
|
43
46
|
if resp.status == 200:
|
|
44
47
|
data = await resp.json()
|
|
@@ -49,14 +52,13 @@ class NetworkRenderStrategy(RenderStrategy):
|
|
|
49
52
|
if ep.get("active") and ep.get("url")
|
|
50
53
|
]
|
|
51
54
|
logger.info(
|
|
52
|
-
f"Successfully got {len(self.endpoints)} official T2I endpoints."
|
|
55
|
+
f"Successfully got {len(self.endpoints)} official T2I endpoints.",
|
|
53
56
|
)
|
|
54
57
|
except Exception as e:
|
|
55
58
|
logger.error(f"Failed to get official endpoints: {e}")
|
|
56
59
|
|
|
57
60
|
def _clean_url(self, url: str):
|
|
58
|
-
|
|
59
|
-
url = url[:-1]
|
|
61
|
+
url = url.removesuffix("/")
|
|
60
62
|
if not url.endswith("text2img"):
|
|
61
63
|
url += "/text2img"
|
|
62
64
|
return url
|
|
@@ -69,7 +71,6 @@ class NetworkRenderStrategy(RenderStrategy):
|
|
|
69
71
|
options: dict | None = None,
|
|
70
72
|
) -> str:
|
|
71
73
|
"""使用自定义文转图模板"""
|
|
72
|
-
|
|
73
74
|
default_options = {"full_page": True, "type": "jpeg", "quality": 40}
|
|
74
75
|
if options:
|
|
75
76
|
default_options |= options
|
|
@@ -89,21 +90,26 @@ class NetworkRenderStrategy(RenderStrategy):
|
|
|
89
90
|
if return_url:
|
|
90
91
|
ssl_context = ssl.create_default_context(cafile=certifi.where())
|
|
91
92
|
connector = aiohttp.TCPConnector(ssl=ssl_context)
|
|
92
|
-
async with
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
93
|
+
async with (
|
|
94
|
+
aiohttp.ClientSession(
|
|
95
|
+
trust_env=True,
|
|
96
|
+
connector=connector,
|
|
97
|
+
) as session,
|
|
98
|
+
session.post(
|
|
99
|
+
f"{endpoint}/generate",
|
|
100
|
+
json=post_data,
|
|
101
|
+
) as resp,
|
|
102
|
+
):
|
|
103
|
+
if resp.status == 200:
|
|
104
|
+
ret = await resp.json()
|
|
105
|
+
return f"{endpoint}/{ret['data']['id']}"
|
|
106
|
+
raise Exception(f"HTTP {resp.status}")
|
|
103
107
|
else:
|
|
104
108
|
# download_image_by_url 失败时抛异常
|
|
105
109
|
return await download_image_by_url(
|
|
106
|
-
f"{endpoint}/generate",
|
|
110
|
+
f"{endpoint}/generate",
|
|
111
|
+
post=True,
|
|
112
|
+
post_data=post_data,
|
|
107
113
|
)
|
|
108
114
|
except Exception as e:
|
|
109
115
|
last_exception = e
|
|
@@ -114,15 +120,18 @@ class NetworkRenderStrategy(RenderStrategy):
|
|
|
114
120
|
raise RuntimeError(f"All endpoints failed: {last_exception}")
|
|
115
121
|
|
|
116
122
|
async def render(
|
|
117
|
-
self,
|
|
123
|
+
self,
|
|
124
|
+
text: str,
|
|
125
|
+
return_url: bool = False,
|
|
126
|
+
template_name: str | None = "base",
|
|
118
127
|
) -> str:
|
|
119
|
-
"""
|
|
120
|
-
返回图像的文件路径
|
|
121
|
-
"""
|
|
128
|
+
"""返回图像的文件路径"""
|
|
122
129
|
if not template_name:
|
|
123
130
|
template_name = "base"
|
|
124
131
|
tmpl_str = await self.get_template(name=template_name)
|
|
125
132
|
text = text.replace("`", "\\`")
|
|
126
133
|
return await self.render_custom_template(
|
|
127
|
-
tmpl_str,
|
|
134
|
+
tmpl_str,
|
|
135
|
+
{"text": text, "version": f"v{VERSION}"},
|
|
136
|
+
return_url,
|
|
128
137
|
)
|
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
from .network_strategy import NetworkRenderStrategy
|
|
2
|
-
from .local_strategy import LocalRenderStrategy
|
|
3
1
|
from astrbot.core.log import LogManager
|
|
4
2
|
|
|
3
|
+
from .local_strategy import LocalRenderStrategy
|
|
4
|
+
from .network_strategy import NetworkRenderStrategy
|
|
5
|
+
|
|
5
6
|
logger = LogManager.GetLogger(log_name="astrbot")
|
|
6
7
|
|
|
7
8
|
|
|
@@ -30,7 +31,10 @@ class HtmlRenderer:
|
|
|
30
31
|
@example: 参见 https://astrbot.app 插件开发部分。
|
|
31
32
|
"""
|
|
32
33
|
return await self.network_strategy.render_custom_template(
|
|
33
|
-
tmpl_str,
|
|
34
|
+
tmpl_str,
|
|
35
|
+
tmpl_data,
|
|
36
|
+
return_url,
|
|
37
|
+
options,
|
|
34
38
|
)
|
|
35
39
|
|
|
36
40
|
async def render_t2i(
|
|
@@ -44,11 +48,13 @@ class HtmlRenderer:
|
|
|
44
48
|
if use_network:
|
|
45
49
|
try:
|
|
46
50
|
return await self.network_strategy.render(
|
|
47
|
-
text,
|
|
51
|
+
text,
|
|
52
|
+
return_url=return_url,
|
|
53
|
+
template_name=template_name,
|
|
48
54
|
)
|
|
49
55
|
except BaseException as e:
|
|
50
56
|
logger.error(
|
|
51
|
-
f"Failed to render image via AstrBot API: {e}. Falling back to local rendering."
|
|
57
|
+
f"Failed to render image via AstrBot API: {e}. Falling back to local rendering.",
|
|
52
58
|
)
|
|
53
59
|
return await self.local_strategy.render(text)
|
|
54
60
|
else:
|
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
import shutil
|
|
5
|
+
|
|
5
6
|
from astrbot.core.utils.astrbot_path import get_astrbot_data_path, get_astrbot_path
|
|
6
7
|
|
|
7
8
|
|
|
8
9
|
class TemplateManager:
|
|
9
|
-
"""
|
|
10
|
-
负责管理 t2i HTML 模板的 CRUD 和重置操作。
|
|
10
|
+
"""负责管理 t2i HTML 模板的 CRUD 和重置操作。
|
|
11
11
|
采用“用户覆盖内置”策略:用户模板存储在 data 目录中,并优先于内置模板加载。
|
|
12
12
|
所有创建、更新、删除操作仅影响用户目录,以确保更新框架时用户数据安全。
|
|
13
13
|
"""
|
|
@@ -16,7 +16,12 @@ class TemplateManager:
|
|
|
16
16
|
|
|
17
17
|
def __init__(self):
|
|
18
18
|
self.builtin_template_dir = os.path.join(
|
|
19
|
-
get_astrbot_path(),
|
|
19
|
+
get_astrbot_path(),
|
|
20
|
+
"astrbot",
|
|
21
|
+
"core",
|
|
22
|
+
"utils",
|
|
23
|
+
"t2i",
|
|
24
|
+
"template",
|
|
20
25
|
)
|
|
21
26
|
self.user_template_dir = os.path.join(get_astrbot_data_path(), "t2i_templates")
|
|
22
27
|
|
|
@@ -43,12 +48,11 @@ class TemplateManager:
|
|
|
43
48
|
|
|
44
49
|
def _read_file(self, path: str) -> str:
|
|
45
50
|
"""读取文件内容。"""
|
|
46
|
-
with open(path,
|
|
51
|
+
with open(path, encoding="utf-8") as f:
|
|
47
52
|
return f.read()
|
|
48
53
|
|
|
49
54
|
def list_templates(self) -> list[dict]:
|
|
50
|
-
"""
|
|
51
|
-
列出所有可用模板。
|
|
55
|
+
"""列出所有可用模板。
|
|
52
56
|
该列表是内置模板和用户模板的合并视图,用户模板将覆盖同名的内置模板。
|
|
53
57
|
"""
|
|
54
58
|
dirs_to_scan = [self.builtin_template_dir, self.user_template_dir]
|
|
@@ -63,8 +67,7 @@ class TemplateManager:
|
|
|
63
67
|
]
|
|
64
68
|
|
|
65
69
|
def get_template(self, name: str) -> str:
|
|
66
|
-
"""
|
|
67
|
-
获取指定模板的内容。
|
|
70
|
+
"""获取指定模板的内容。
|
|
68
71
|
优先从用户目录加载,如果不存在则回退到内置目录。
|
|
69
72
|
"""
|
|
70
73
|
user_path = self._get_user_template_path(name)
|
|
@@ -86,8 +89,7 @@ class TemplateManager:
|
|
|
86
89
|
f.write(content)
|
|
87
90
|
|
|
88
91
|
def update_template(self, name: str, content: str):
|
|
89
|
-
"""
|
|
90
|
-
更新一个模板。此操作始终写入用户目录。
|
|
92
|
+
"""更新一个模板。此操作始终写入用户目录。
|
|
91
93
|
如果更新的是一个内置模板,此操作实际上会在用户目录中创建一个修改后的副本,
|
|
92
94
|
从而实现对内置模板的“覆盖”。
|
|
93
95
|
"""
|
|
@@ -96,8 +98,7 @@ class TemplateManager:
|
|
|
96
98
|
f.write(content)
|
|
97
99
|
|
|
98
100
|
def delete_template(self, name: str):
|
|
99
|
-
"""
|
|
100
|
-
仅删除用户目录中的模板文件。
|
|
101
|
+
"""仅删除用户目录中的模板文件。
|
|
101
102
|
如果删除的是一个覆盖了内置模板的用户模板,这将有效地“恢复”到内置版本。
|
|
102
103
|
"""
|
|
103
104
|
path = self._get_user_template_path(name)
|
|
@@ -106,7 +107,5 @@ class TemplateManager:
|
|
|
106
107
|
os.remove(path)
|
|
107
108
|
|
|
108
109
|
def reset_default_template(self):
|
|
109
|
-
"""
|
|
110
|
-
将核心模板从内置目录强制重置到用户目录。
|
|
111
|
-
"""
|
|
110
|
+
"""将核心模板从内置目录强制重置到用户目录。"""
|
|
112
111
|
self._copy_core_templates(overwrite=True)
|
|
@@ -1,10 +1,11 @@
|
|
|
1
|
+
import asyncio
|
|
1
2
|
import base64
|
|
2
|
-
import wave
|
|
3
3
|
import os
|
|
4
4
|
import subprocess
|
|
5
|
-
from io import BytesIO
|
|
6
|
-
import asyncio
|
|
7
5
|
import tempfile
|
|
6
|
+
import wave
|
|
7
|
+
from io import BytesIO
|
|
8
|
+
|
|
8
9
|
from astrbot.core import logger
|
|
9
10
|
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
10
11
|
|
|
@@ -35,7 +36,7 @@ async def wav_to_tencent_silk(wav_path: str, output_path: str) -> int:
|
|
|
35
36
|
import pilk
|
|
36
37
|
except (ImportError, ModuleNotFoundError) as _:
|
|
37
38
|
raise Exception(
|
|
38
|
-
"pilk 模块未安装,请前往管理面板->控制台->安装pip库 安装 pilk 这个库"
|
|
39
|
+
"pilk 模块未安装,请前往管理面板->控制台->安装pip库 安装 pilk 这个库",
|
|
39
40
|
)
|
|
40
41
|
# with wave.open(wav_path, 'rb') as wav:
|
|
41
42
|
# wav_data = wav.readframes(wav.getnframes())
|
|
@@ -60,8 +61,7 @@ async def wav_to_tencent_silk(wav_path: str, output_path: str) -> int:
|
|
|
60
61
|
|
|
61
62
|
|
|
62
63
|
async def convert_to_pcm_wav(input_path: str, output_path: str) -> str:
|
|
63
|
-
"""
|
|
64
|
-
将 MP3 或其他音频格式转换为 PCM 16bit WAV,采样率24000Hz,单声道。
|
|
64
|
+
"""将 MP3 或其他音频格式转换为 PCM 16bit WAV,采样率24000Hz,单声道。
|
|
65
65
|
若转换失败则抛出异常。
|
|
66
66
|
"""
|
|
67
67
|
try:
|
|
@@ -99,13 +99,11 @@ async def convert_to_pcm_wav(input_path: str, output_path: str) -> str:
|
|
|
99
99
|
|
|
100
100
|
if os.path.exists(output_path) and os.path.getsize(output_path) > 0:
|
|
101
101
|
return output_path
|
|
102
|
-
|
|
103
|
-
raise RuntimeError("生成的WAV文件不存在或为空")
|
|
102
|
+
raise RuntimeError("生成的WAV文件不存在或为空")
|
|
104
103
|
|
|
105
104
|
|
|
106
105
|
async def audio_to_tencent_silk_base64(audio_path: str) -> tuple[str, float]:
|
|
107
|
-
"""
|
|
108
|
-
将 MP3/WAV 文件转为 Tencent Silk 并返回 base64 编码与时长(秒)。
|
|
106
|
+
"""将 MP3/WAV 文件转为 Tencent Silk 并返回 base64 编码与时长(秒)。
|
|
109
107
|
|
|
110
108
|
参数:
|
|
111
109
|
- audio_path: 输入音频文件路径(.mp3 或 .wav)
|
|
@@ -125,7 +123,9 @@ async def audio_to_tencent_silk_base64(audio_path: str) -> tuple[str, float]:
|
|
|
125
123
|
# 是否需要转换为 WAV
|
|
126
124
|
ext = os.path.splitext(audio_path)[1].lower()
|
|
127
125
|
temp_wav = tempfile.NamedTemporaryFile(
|
|
128
|
-
suffix=".wav",
|
|
126
|
+
suffix=".wav",
|
|
127
|
+
delete=False,
|
|
128
|
+
dir=temp_dir,
|
|
129
129
|
).name
|
|
130
130
|
|
|
131
131
|
if ext != ".wav":
|
|
@@ -140,12 +140,18 @@ async def audio_to_tencent_silk_base64(audio_path: str) -> tuple[str, float]:
|
|
|
140
140
|
rate = wav_file.getframerate()
|
|
141
141
|
|
|
142
142
|
silk_path = tempfile.NamedTemporaryFile(
|
|
143
|
-
suffix=".silk",
|
|
143
|
+
suffix=".silk",
|
|
144
|
+
delete=False,
|
|
145
|
+
dir=temp_dir,
|
|
144
146
|
).name
|
|
145
147
|
|
|
146
148
|
try:
|
|
147
149
|
duration = await asyncio.to_thread(
|
|
148
|
-
pilk.encode,
|
|
150
|
+
pilk.encode,
|
|
151
|
+
wav_path,
|
|
152
|
+
silk_path,
|
|
153
|
+
pcm_rate=rate,
|
|
154
|
+
tencent=True,
|
|
149
155
|
)
|
|
150
156
|
|
|
151
157
|
with open(silk_path, "rb") as f:
|
|
@@ -38,15 +38,15 @@ class VersionComparator:
|
|
|
38
38
|
for i in range(length):
|
|
39
39
|
if v1_parts[i] > v2_parts[i]:
|
|
40
40
|
return 1
|
|
41
|
-
|
|
41
|
+
if v1_parts[i] < v2_parts[i]:
|
|
42
42
|
return -1
|
|
43
43
|
|
|
44
44
|
# 比较预发布标签
|
|
45
45
|
if v1_prerelease is None and v2_prerelease is not None:
|
|
46
46
|
return 1 # 没有预发布标签的版本高于有预发布标签的版本
|
|
47
|
-
|
|
47
|
+
if v1_prerelease is not None and v2_prerelease is None:
|
|
48
48
|
return -1 # 有预发布标签的版本低于没有预发布标签的版本
|
|
49
|
-
|
|
49
|
+
if v1_prerelease is not None and v2_prerelease is not None:
|
|
50
50
|
len_pre = max(len(v1_prerelease), len(v2_prerelease))
|
|
51
51
|
for i in range(len_pre):
|
|
52
52
|
p1 = v1_prerelease[i] if i < len(v1_prerelease) else None
|
|
@@ -54,21 +54,18 @@ class VersionComparator:
|
|
|
54
54
|
|
|
55
55
|
if p1 is None and p2 is not None:
|
|
56
56
|
return -1
|
|
57
|
-
|
|
57
|
+
if p1 is not None and p2 is None:
|
|
58
58
|
return 1
|
|
59
|
-
|
|
59
|
+
if isinstance(p1, int) and isinstance(p2, str):
|
|
60
60
|
return -1
|
|
61
|
-
|
|
61
|
+
if isinstance(p1, str) and isinstance(p2, int):
|
|
62
62
|
return 1
|
|
63
|
-
|
|
63
|
+
if (isinstance(p1, int) and isinstance(p2, int)) or (
|
|
64
|
+
isinstance(p1, str) and isinstance(p2, str)
|
|
65
|
+
):
|
|
64
66
|
if p1 > p2:
|
|
65
67
|
return 1
|
|
66
|
-
|
|
67
|
-
return -1
|
|
68
|
-
elif isinstance(p1, str) and isinstance(p2, str):
|
|
69
|
-
if p1 > p2:
|
|
70
|
-
return 1
|
|
71
|
-
elif p1 < p2:
|
|
68
|
+
if p1 < p2:
|
|
72
69
|
return -1
|
|
73
70
|
return 0 # 预发布标签完全相同
|
|
74
71
|
|
astrbot/core/zip_updator.py
CHANGED
|
@@ -1,14 +1,14 @@
|
|
|
1
|
-
import aiohttp
|
|
2
1
|
import os
|
|
3
2
|
import re
|
|
4
|
-
import zipfile
|
|
5
3
|
import shutil
|
|
6
|
-
|
|
7
4
|
import ssl
|
|
5
|
+
import zipfile
|
|
6
|
+
|
|
7
|
+
import aiohttp
|
|
8
8
|
import certifi
|
|
9
9
|
|
|
10
|
-
from astrbot.core.utils.io import on_error, download_file
|
|
11
10
|
from astrbot.core import logger
|
|
11
|
+
from astrbot.core.utils.io import download_file, on_error
|
|
12
12
|
from astrbot.core.utils.version_comparator import VersionComparator
|
|
13
13
|
|
|
14
14
|
|
|
@@ -18,7 +18,10 @@ class ReleaseInfo:
|
|
|
18
18
|
body: str
|
|
19
19
|
|
|
20
20
|
def __init__(
|
|
21
|
-
self,
|
|
21
|
+
self,
|
|
22
|
+
version: str = "",
|
|
23
|
+
published_at: str = "",
|
|
24
|
+
body: str = "",
|
|
22
25
|
) -> None:
|
|
23
26
|
self.version = version
|
|
24
27
|
self.published_at = published_at
|
|
@@ -34,29 +37,31 @@ class RepoZipUpdator:
|
|
|
34
37
|
self.rm_on_error = on_error
|
|
35
38
|
|
|
36
39
|
async def fetch_release_info(self, url: str, latest: bool = True) -> list:
|
|
37
|
-
"""
|
|
38
|
-
请求版本信息。
|
|
40
|
+
"""请求版本信息。
|
|
39
41
|
返回一个列表,每个元素是一个字典,包含版本号、发布时间、更新内容、commit hash等信息。
|
|
40
42
|
"""
|
|
41
43
|
try:
|
|
42
44
|
ssl_context = ssl.create_default_context(
|
|
43
|
-
cafile=certifi.where()
|
|
45
|
+
cafile=certifi.where(),
|
|
44
46
|
) # 新增:创建基于 certifi 的 SSL 上下文
|
|
45
47
|
connector = aiohttp.TCPConnector(
|
|
46
|
-
ssl=ssl_context
|
|
48
|
+
ssl=ssl_context,
|
|
47
49
|
) # 新增:使用 TCPConnector 指定 SSL 上下文
|
|
48
|
-
async with
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
50
|
+
async with (
|
|
51
|
+
aiohttp.ClientSession(
|
|
52
|
+
trust_env=True,
|
|
53
|
+
connector=connector,
|
|
54
|
+
) as session,
|
|
55
|
+
session.get(url) as response,
|
|
56
|
+
):
|
|
57
|
+
# 检查 HTTP 状态码
|
|
58
|
+
if response.status != 200:
|
|
59
|
+
text = await response.text()
|
|
60
|
+
logger.error(
|
|
61
|
+
f"请求 {url} 失败,状态码: {response.status}, 内容: {text}",
|
|
62
|
+
)
|
|
63
|
+
raise Exception(f"请求失败,状态码: {response.status}")
|
|
64
|
+
result = await response.json()
|
|
60
65
|
if not result:
|
|
61
66
|
return []
|
|
62
67
|
# if latest:
|
|
@@ -72,7 +77,7 @@ class RepoZipUpdator:
|
|
|
72
77
|
"body": release["body"],
|
|
73
78
|
"tag_name": release["tag_name"],
|
|
74
79
|
"zipball_url": release["zipball_url"],
|
|
75
|
-
}
|
|
80
|
+
},
|
|
76
81
|
)
|
|
77
82
|
except Exception as e:
|
|
78
83
|
logger.error(f"解析版本信息时发生异常: {e}")
|
|
@@ -80,8 +85,7 @@ class RepoZipUpdator:
|
|
|
80
85
|
return ret
|
|
81
86
|
|
|
82
87
|
def github_api_release_parser(self, releases: list) -> list:
|
|
83
|
-
"""
|
|
84
|
-
解析 GitHub API 返回的 releases 信息。
|
|
88
|
+
"""解析 GitHub API 返回的 releases 信息。
|
|
85
89
|
返回一个列表,每个元素是一个字典,包含版本号、发布时间、更新内容、commit hash等信息。
|
|
86
90
|
"""
|
|
87
91
|
ret = []
|
|
@@ -93,22 +97,25 @@ class RepoZipUpdator:
|
|
|
93
97
|
"body": release["body"],
|
|
94
98
|
"tag_name": release["tag_name"],
|
|
95
99
|
"zipball_url": release["zipball_url"],
|
|
96
|
-
}
|
|
100
|
+
},
|
|
97
101
|
)
|
|
98
102
|
return ret
|
|
99
103
|
|
|
100
104
|
def unzip(self):
|
|
101
|
-
raise NotImplementedError
|
|
105
|
+
raise NotImplementedError
|
|
102
106
|
|
|
103
107
|
async def update(self):
|
|
104
|
-
raise NotImplementedError
|
|
108
|
+
raise NotImplementedError
|
|
105
109
|
|
|
106
110
|
def compare_version(self, v1: str, v2: str) -> int:
|
|
107
111
|
"""Semver 版本比较"""
|
|
108
112
|
return VersionComparator.compare_version(v1, v2)
|
|
109
113
|
|
|
110
114
|
async def check_update(
|
|
111
|
-
self,
|
|
115
|
+
self,
|
|
116
|
+
url: str,
|
|
117
|
+
current_version: str,
|
|
118
|
+
consider_prerelease: bool = True,
|
|
112
119
|
) -> ReleaseInfo | None:
|
|
113
120
|
update_data = await self.fetch_release_info(url)
|
|
114
121
|
|
|
@@ -157,7 +164,7 @@ class RepoZipUpdator:
|
|
|
157
164
|
releases = await self.fetch_release_info(url=release_url)
|
|
158
165
|
except Exception as e:
|
|
159
166
|
logger.warning(
|
|
160
|
-
f"获取 {author}/{repo} 的 GitHub Releases 失败: {e},将尝试下载默认分支"
|
|
167
|
+
f"获取 {author}/{repo} 的 GitHub Releases 失败: {e},将尝试下载默认分支",
|
|
161
168
|
)
|
|
162
169
|
releases = []
|
|
163
170
|
if not releases:
|
|
@@ -173,7 +180,7 @@ class RepoZipUpdator:
|
|
|
173
180
|
proxy = proxy.rstrip("/")
|
|
174
181
|
release_url = f"{proxy}/{release_url}"
|
|
175
182
|
logger.info(
|
|
176
|
-
f"检查到设置了镜像站,将使用镜像站下载 {author}/{repo} 仓库源码: {release_url}"
|
|
183
|
+
f"检查到设置了镜像站,将使用镜像站下载 {author}/{repo} 仓库源码: {release_url}",
|
|
177
184
|
)
|
|
178
185
|
|
|
179
186
|
await download_file(release_url, target_path + ".zip")
|
|
@@ -194,13 +201,10 @@ class RepoZipUpdator:
|
|
|
194
201
|
repo = match.group(2)
|
|
195
202
|
branch = match.group(4)
|
|
196
203
|
return author, repo, branch
|
|
197
|
-
|
|
198
|
-
raise ValueError("无效的 GitHub URL")
|
|
204
|
+
raise ValueError("无效的 GitHub URL")
|
|
199
205
|
|
|
200
206
|
def unzip_file(self, zip_path: str, target_dir: str):
|
|
201
|
-
"""
|
|
202
|
-
解压缩文件, 并将压缩包内**第一个**文件夹内的文件移动到 target_dir
|
|
203
|
-
"""
|
|
207
|
+
"""解压缩文件, 并将压缩包内**第一个**文件夹内的文件移动到 target_dir"""
|
|
204
208
|
os.makedirs(target_dir, exist_ok=True)
|
|
205
209
|
update_dir = ""
|
|
206
210
|
with zipfile.ZipFile(zip_path, "r") as z:
|
|
@@ -213,20 +217,19 @@ class RepoZipUpdator:
|
|
|
213
217
|
if os.path.isdir(os.path.join(target_dir, update_dir, f)):
|
|
214
218
|
if os.path.exists(os.path.join(target_dir, f)):
|
|
215
219
|
shutil.rmtree(os.path.join(target_dir, f), onerror=on_error)
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
os.remove(os.path.join(target_dir, f))
|
|
220
|
+
elif os.path.exists(os.path.join(target_dir, f)):
|
|
221
|
+
os.remove(os.path.join(target_dir, f))
|
|
219
222
|
shutil.move(os.path.join(target_dir, update_dir, f), target_dir)
|
|
220
223
|
|
|
221
224
|
try:
|
|
222
225
|
logger.debug(
|
|
223
|
-
f"删除临时更新文件: {zip_path} 和 {os.path.join(target_dir, update_dir)}"
|
|
226
|
+
f"删除临时更新文件: {zip_path} 和 {os.path.join(target_dir, update_dir)}",
|
|
224
227
|
)
|
|
225
228
|
shutil.rmtree(os.path.join(target_dir, update_dir), onerror=on_error)
|
|
226
229
|
os.remove(zip_path)
|
|
227
230
|
except BaseException:
|
|
228
231
|
logger.warning(
|
|
229
|
-
f"删除更新文件失败,可以手动删除 {zip_path} 和 {os.path.join(target_dir, update_dir)}"
|
|
232
|
+
f"删除更新文件失败,可以手动删除 {zip_path} 和 {os.path.join(target_dir, update_dir)}",
|
|
230
233
|
)
|
|
231
234
|
|
|
232
235
|
def format_name(self, name: str) -> str:
|
|
@@ -1,31 +1,31 @@
|
|
|
1
1
|
from .auth import AuthRoute
|
|
2
|
-
from .plugin import PluginRoute
|
|
3
|
-
from .config import ConfigRoute
|
|
4
|
-
from .update import UpdateRoute
|
|
5
|
-
from .stat import StatRoute
|
|
6
|
-
from .log import LogRoute
|
|
7
|
-
from .static_file import StaticFileRoute
|
|
8
2
|
from .chat import ChatRoute
|
|
9
|
-
from .
|
|
3
|
+
from .config import ConfigRoute
|
|
10
4
|
from .conversation import ConversationRoute
|
|
11
5
|
from .file import FileRoute
|
|
12
|
-
from .session_management import SessionManagementRoute
|
|
13
|
-
from .persona import PersonaRoute
|
|
14
6
|
from .knowledge_base import KnowledgeBaseRoute
|
|
7
|
+
from .log import LogRoute
|
|
8
|
+
from .persona import PersonaRoute
|
|
9
|
+
from .plugin import PluginRoute
|
|
10
|
+
from .session_management import SessionManagementRoute
|
|
11
|
+
from .stat import StatRoute
|
|
12
|
+
from .static_file import StaticFileRoute
|
|
13
|
+
from .tools import ToolsRoute
|
|
14
|
+
from .update import UpdateRoute
|
|
15
15
|
|
|
16
16
|
__all__ = [
|
|
17
17
|
"AuthRoute",
|
|
18
|
-
"PluginRoute",
|
|
19
|
-
"ConfigRoute",
|
|
20
|
-
"UpdateRoute",
|
|
21
|
-
"StatRoute",
|
|
22
|
-
"LogRoute",
|
|
23
|
-
"StaticFileRoute",
|
|
24
18
|
"ChatRoute",
|
|
25
|
-
"
|
|
19
|
+
"ConfigRoute",
|
|
26
20
|
"ConversationRoute",
|
|
27
21
|
"FileRoute",
|
|
28
|
-
"SessionManagementRoute",
|
|
29
|
-
"PersonaRoute",
|
|
30
22
|
"KnowledgeBaseRoute",
|
|
23
|
+
"LogRoute",
|
|
24
|
+
"PersonaRoute",
|
|
25
|
+
"PluginRoute",
|
|
26
|
+
"SessionManagementRoute",
|
|
27
|
+
"StatRoute",
|
|
28
|
+
"StaticFileRoute",
|
|
29
|
+
"ToolsRoute",
|
|
30
|
+
"UpdateRoute",
|
|
31
31
|
]
|
astrbot/dashboard/routes/auth.py
CHANGED
|
@@ -1,10 +1,13 @@
|
|
|
1
|
-
import jwt
|
|
2
|
-
import datetime
|
|
3
1
|
import asyncio
|
|
4
|
-
|
|
2
|
+
import datetime
|
|
3
|
+
|
|
4
|
+
import jwt
|
|
5
5
|
from quart import request
|
|
6
|
-
|
|
6
|
+
|
|
7
7
|
from astrbot import logger
|
|
8
|
+
from astrbot.core import DEMO_MODE
|
|
9
|
+
|
|
10
|
+
from .route import Response, Route, RouteContext
|
|
8
11
|
|
|
9
12
|
|
|
10
13
|
class AuthRoute(Route):
|
|
@@ -37,13 +40,12 @@ class AuthRoute(Route):
|
|
|
37
40
|
"token": self.generate_jwt(username),
|
|
38
41
|
"username": username,
|
|
39
42
|
"change_pwd_hint": change_pwd_hint,
|
|
40
|
-
}
|
|
43
|
+
},
|
|
41
44
|
)
|
|
42
45
|
.__dict__
|
|
43
46
|
)
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
return Response().error("用户名或密码错误").__dict__
|
|
47
|
+
await asyncio.sleep(3)
|
|
48
|
+
return Response().error("用户名或密码错误").__dict__
|
|
47
49
|
|
|
48
50
|
async def edit_account(self):
|
|
49
51
|
if DEMO_MODE:
|