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.
- astrbot/api/__init__.py +16 -4
- astrbot/api/all.py +2 -1
- astrbot/api/event/__init__.py +5 -6
- astrbot/api/event/filter/__init__.py +37 -34
- astrbot/api/platform/__init__.py +7 -8
- astrbot/api/provider/__init__.py +8 -7
- astrbot/api/star/__init__.py +3 -4
- astrbot/api/util/__init__.py +2 -2
- astrbot/cli/__init__.py +1 -0
- astrbot/cli/__main__.py +18 -197
- astrbot/cli/commands/__init__.py +6 -0
- astrbot/cli/commands/cmd_conf.py +209 -0
- astrbot/cli/commands/cmd_init.py +56 -0
- astrbot/cli/commands/cmd_plug.py +245 -0
- astrbot/cli/commands/cmd_run.py +62 -0
- astrbot/cli/utils/__init__.py +18 -0
- astrbot/cli/utils/basic.py +76 -0
- astrbot/cli/utils/plugin.py +246 -0
- astrbot/cli/utils/version_comparator.py +90 -0
- astrbot/core/__init__.py +17 -19
- astrbot/core/agent/agent.py +14 -0
- astrbot/core/agent/handoff.py +38 -0
- astrbot/core/agent/hooks.py +30 -0
- astrbot/core/agent/mcp_client.py +385 -0
- astrbot/core/agent/message.py +175 -0
- astrbot/core/agent/response.py +14 -0
- astrbot/core/agent/run_context.py +22 -0
- astrbot/core/agent/runners/__init__.py +3 -0
- astrbot/core/agent/runners/base.py +65 -0
- astrbot/core/agent/runners/coze/coze_agent_runner.py +367 -0
- astrbot/core/agent/runners/coze/coze_api_client.py +324 -0
- astrbot/core/agent/runners/dashscope/dashscope_agent_runner.py +403 -0
- astrbot/core/agent/runners/dify/dify_agent_runner.py +336 -0
- astrbot/core/agent/runners/dify/dify_api_client.py +195 -0
- astrbot/core/agent/runners/tool_loop_agent_runner.py +400 -0
- astrbot/core/agent/tool.py +285 -0
- astrbot/core/agent/tool_executor.py +17 -0
- astrbot/core/astr_agent_context.py +19 -0
- astrbot/core/astr_agent_hooks.py +36 -0
- astrbot/core/astr_agent_run_util.py +80 -0
- astrbot/core/astr_agent_tool_exec.py +246 -0
- astrbot/core/astrbot_config_mgr.py +275 -0
- astrbot/core/config/__init__.py +2 -2
- astrbot/core/config/astrbot_config.py +60 -20
- astrbot/core/config/default.py +1972 -453
- astrbot/core/config/i18n_utils.py +110 -0
- astrbot/core/conversation_mgr.py +285 -75
- astrbot/core/core_lifecycle.py +167 -62
- astrbot/core/db/__init__.py +305 -102
- astrbot/core/db/migration/helper.py +69 -0
- astrbot/core/db/migration/migra_3_to_4.py +357 -0
- astrbot/core/db/migration/migra_45_to_46.py +44 -0
- astrbot/core/db/migration/migra_webchat_session.py +131 -0
- astrbot/core/db/migration/shared_preferences_v3.py +48 -0
- astrbot/core/db/migration/sqlite_v3.py +497 -0
- astrbot/core/db/po.py +259 -55
- astrbot/core/db/sqlite.py +773 -528
- astrbot/core/db/vec_db/base.py +73 -0
- astrbot/core/db/vec_db/faiss_impl/__init__.py +3 -0
- astrbot/core/db/vec_db/faiss_impl/document_storage.py +392 -0
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +93 -0
- astrbot/core/db/vec_db/faiss_impl/sqlite_init.sql +17 -0
- astrbot/core/db/vec_db/faiss_impl/vec_db.py +204 -0
- astrbot/core/event_bus.py +26 -22
- astrbot/core/exceptions.py +9 -0
- astrbot/core/file_token_service.py +98 -0
- astrbot/core/initial_loader.py +19 -10
- astrbot/core/knowledge_base/chunking/__init__.py +9 -0
- astrbot/core/knowledge_base/chunking/base.py +25 -0
- astrbot/core/knowledge_base/chunking/fixed_size.py +59 -0
- astrbot/core/knowledge_base/chunking/recursive.py +161 -0
- astrbot/core/knowledge_base/kb_db_sqlite.py +301 -0
- astrbot/core/knowledge_base/kb_helper.py +642 -0
- astrbot/core/knowledge_base/kb_mgr.py +330 -0
- astrbot/core/knowledge_base/models.py +120 -0
- astrbot/core/knowledge_base/parsers/__init__.py +13 -0
- astrbot/core/knowledge_base/parsers/base.py +51 -0
- astrbot/core/knowledge_base/parsers/markitdown_parser.py +26 -0
- astrbot/core/knowledge_base/parsers/pdf_parser.py +101 -0
- astrbot/core/knowledge_base/parsers/text_parser.py +42 -0
- astrbot/core/knowledge_base/parsers/url_parser.py +103 -0
- astrbot/core/knowledge_base/parsers/util.py +13 -0
- astrbot/core/knowledge_base/prompts.py +65 -0
- astrbot/core/knowledge_base/retrieval/__init__.py +14 -0
- astrbot/core/knowledge_base/retrieval/hit_stopwords.txt +767 -0
- astrbot/core/knowledge_base/retrieval/manager.py +276 -0
- astrbot/core/knowledge_base/retrieval/rank_fusion.py +142 -0
- astrbot/core/knowledge_base/retrieval/sparse_retriever.py +136 -0
- astrbot/core/log.py +21 -15
- astrbot/core/message/components.py +413 -287
- astrbot/core/message/message_event_result.py +35 -24
- astrbot/core/persona_mgr.py +192 -0
- astrbot/core/pipeline/__init__.py +14 -14
- 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 +13 -14
- astrbot/core/pipeline/content_safety_check/strategies/keywords.py +2 -1
- astrbot/core/pipeline/content_safety_check/strategies/strategy.py +6 -6
- astrbot/core/pipeline/context.py +7 -1
- astrbot/core/pipeline/context_utils.py +107 -0
- astrbot/core/pipeline/preprocess_stage/stage.py +63 -36
- astrbot/core/pipeline/process_stage/method/agent_request.py +48 -0
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py +464 -0
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +202 -0
- astrbot/core/pipeline/process_stage/method/star_request.py +26 -32
- astrbot/core/pipeline/process_stage/stage.py +21 -15
- astrbot/core/pipeline/process_stage/utils.py +125 -0
- astrbot/core/pipeline/rate_limit_check/stage.py +34 -36
- astrbot/core/pipeline/respond/stage.py +142 -101
- astrbot/core/pipeline/result_decorate/stage.py +124 -57
- astrbot/core/pipeline/scheduler.py +21 -16
- astrbot/core/pipeline/session_status_check/stage.py +37 -0
- astrbot/core/pipeline/stage.py +11 -76
- astrbot/core/pipeline/waking_check/stage.py +69 -33
- astrbot/core/pipeline/whitelist_check/stage.py +10 -7
- astrbot/core/platform/__init__.py +6 -6
- astrbot/core/platform/astr_message_event.py +107 -129
- astrbot/core/platform/astrbot_message.py +32 -12
- astrbot/core/platform/manager.py +62 -18
- astrbot/core/platform/message_session.py +30 -0
- astrbot/core/platform/platform.py +16 -24
- astrbot/core/platform/platform_metadata.py +9 -4
- astrbot/core/platform/register.py +12 -7
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +136 -60
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +126 -46
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +63 -31
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +30 -26
- astrbot/core/platform/sources/discord/client.py +129 -0
- astrbot/core/platform/sources/discord/components.py +139 -0
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +473 -0
- astrbot/core/platform/sources/discord/discord_platform_event.py +313 -0
- astrbot/core/platform/sources/lark/lark_adapter.py +27 -18
- astrbot/core/platform/sources/lark/lark_event.py +39 -13
- astrbot/core/platform/sources/misskey/misskey_adapter.py +770 -0
- astrbot/core/platform/sources/misskey/misskey_api.py +964 -0
- astrbot/core/platform/sources/misskey/misskey_event.py +163 -0
- astrbot/core/platform/sources/misskey/misskey_utils.py +550 -0
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +149 -33
- 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 +14 -8
- astrbot/core/platform/sources/satori/satori_adapter.py +792 -0
- astrbot/core/platform/sources/satori/satori_event.py +432 -0
- astrbot/core/platform/sources/slack/client.py +164 -0
- astrbot/core/platform/sources/slack/slack_adapter.py +416 -0
- astrbot/core/platform/sources/slack/slack_event.py +253 -0
- astrbot/core/platform/sources/telegram/tg_adapter.py +100 -43
- astrbot/core/platform/sources/telegram/tg_event.py +136 -36
- astrbot/core/platform/sources/webchat/webchat_adapter.py +72 -22
- astrbot/core/platform/sources/webchat/webchat_event.py +46 -22
- astrbot/core/platform/sources/webchat/webchat_queue_mgr.py +35 -0
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +926 -0
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_message_event.py +178 -0
- astrbot/core/platform/sources/wechatpadpro/xml_data_parser.py +159 -0
- astrbot/core/platform/sources/wecom/wecom_adapter.py +169 -27
- astrbot/core/platform/sources/wecom/wecom_event.py +162 -77
- astrbot/core/platform/sources/wecom/wecom_kf.py +279 -0
- astrbot/core/platform/sources/wecom/wecom_kf_message.py +196 -0
- astrbot/core/platform/sources/wecom_ai_bot/WXBizJsonMsgCrypt.py +297 -0
- astrbot/core/platform/sources/wecom_ai_bot/__init__.py +15 -0
- astrbot/core/platform/sources/wecom_ai_bot/ierror.py +19 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +472 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_api.py +417 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +152 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py +153 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +168 -0
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_utils.py +209 -0
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +306 -0
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +186 -0
- astrbot/core/platform_message_history_mgr.py +49 -0
- astrbot/core/provider/__init__.py +2 -3
- astrbot/core/provider/entites.py +8 -8
- astrbot/core/provider/entities.py +154 -98
- astrbot/core/provider/func_tool_manager.py +446 -458
- astrbot/core/provider/manager.py +345 -207
- astrbot/core/provider/provider.py +188 -73
- astrbot/core/provider/register.py +9 -7
- astrbot/core/provider/sources/anthropic_source.py +295 -115
- astrbot/core/provider/sources/azure_tts_source.py +224 -0
- astrbot/core/provider/sources/bailian_rerank_source.py +236 -0
- astrbot/core/provider/sources/dashscope_tts.py +138 -14
- astrbot/core/provider/sources/edge_tts_source.py +24 -19
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +58 -13
- astrbot/core/provider/sources/gemini_embedding_source.py +61 -0
- astrbot/core/provider/sources/gemini_source.py +310 -132
- astrbot/core/provider/sources/gemini_tts_source.py +81 -0
- astrbot/core/provider/sources/groq_source.py +15 -0
- astrbot/core/provider/sources/gsv_selfhosted_source.py +151 -0
- astrbot/core/provider/sources/gsvi_tts_source.py +14 -7
- astrbot/core/provider/sources/minimax_tts_api_source.py +159 -0
- astrbot/core/provider/sources/openai_embedding_source.py +40 -0
- astrbot/core/provider/sources/openai_source.py +241 -145
- astrbot/core/provider/sources/openai_tts_api_source.py +18 -7
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +13 -11
- astrbot/core/provider/sources/vllm_rerank_source.py +71 -0
- astrbot/core/provider/sources/volcengine_tts.py +115 -0
- astrbot/core/provider/sources/whisper_api_source.py +18 -13
- astrbot/core/provider/sources/whisper_selfhosted_source.py +19 -12
- astrbot/core/provider/sources/xinference_rerank_source.py +116 -0
- astrbot/core/provider/sources/xinference_stt_provider.py +197 -0
- astrbot/core/provider/sources/zhipu_source.py +6 -73
- astrbot/core/star/__init__.py +43 -11
- astrbot/core/star/config.py +17 -18
- astrbot/core/star/context.py +362 -138
- astrbot/core/star/filter/__init__.py +4 -3
- astrbot/core/star/filter/command.py +111 -35
- astrbot/core/star/filter/command_group.py +46 -34
- 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 +45 -12
- astrbot/core/star/filter/regex.py +4 -2
- astrbot/core/star/register/__init__.py +19 -15
- astrbot/core/star/register/star.py +41 -13
- astrbot/core/star/register/star_handler.py +236 -86
- astrbot/core/star/session_llm_manager.py +280 -0
- astrbot/core/star/session_plugin_manager.py +170 -0
- astrbot/core/star/star.py +36 -43
- astrbot/core/star/star_handler.py +47 -85
- astrbot/core/star/star_manager.py +442 -260
- astrbot/core/star/star_tools.py +167 -45
- astrbot/core/star/updator.py +17 -20
- astrbot/core/umop_config_router.py +106 -0
- astrbot/core/updator.py +38 -13
- astrbot/core/utils/astrbot_path.py +39 -0
- astrbot/core/utils/command_parser.py +1 -1
- astrbot/core/utils/io.py +119 -60
- astrbot/core/utils/log_pipe.py +1 -1
- astrbot/core/utils/metrics.py +11 -10
- astrbot/core/utils/migra_helper.py +73 -0
- astrbot/core/utils/path_util.py +63 -62
- astrbot/core/utils/pip_installer.py +37 -15
- astrbot/core/utils/session_lock.py +29 -0
- astrbot/core/utils/session_waiter.py +19 -20
- astrbot/core/utils/shared_preferences.py +174 -34
- astrbot/core/utils/t2i/__init__.py +4 -1
- astrbot/core/utils/t2i/local_strategy.py +386 -238
- astrbot/core/utils/t2i/network_strategy.py +109 -49
- astrbot/core/utils/t2i/renderer.py +29 -14
- astrbot/core/utils/t2i/template/astrbot_powershell.html +184 -0
- astrbot/core/utils/t2i/template_manager.py +111 -0
- astrbot/core/utils/tencent_record_helper.py +115 -1
- astrbot/core/utils/version_comparator.py +10 -13
- astrbot/core/zip_updator.py +112 -65
- astrbot/dashboard/routes/__init__.py +20 -13
- astrbot/dashboard/routes/auth.py +20 -9
- astrbot/dashboard/routes/chat.py +297 -141
- astrbot/dashboard/routes/config.py +652 -55
- astrbot/dashboard/routes/conversation.py +107 -37
- astrbot/dashboard/routes/file.py +26 -0
- astrbot/dashboard/routes/knowledge_base.py +1244 -0
- astrbot/dashboard/routes/log.py +27 -2
- astrbot/dashboard/routes/persona.py +202 -0
- astrbot/dashboard/routes/plugin.py +197 -139
- astrbot/dashboard/routes/route.py +27 -7
- astrbot/dashboard/routes/session_management.py +354 -0
- astrbot/dashboard/routes/stat.py +85 -18
- astrbot/dashboard/routes/static_file.py +5 -2
- astrbot/dashboard/routes/t2i.py +233 -0
- astrbot/dashboard/routes/tools.py +184 -120
- astrbot/dashboard/routes/update.py +59 -36
- astrbot/dashboard/server.py +96 -36
- astrbot/dashboard/utils.py +165 -0
- astrbot-4.7.0.dist-info/METADATA +294 -0
- astrbot-4.7.0.dist-info/RECORD +274 -0
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/WHEEL +1 -1
- astrbot/core/db/plugin/sqlite_impl.py +0 -112
- astrbot/core/db/sqlite_init.sql +0 -50
- astrbot/core/pipeline/platform_compatibility/stage.py +0 -56
- astrbot/core/pipeline/process_stage/method/llm_request.py +0 -606
- astrbot/core/platform/sources/gewechat/client.py +0 -806
- astrbot/core/platform/sources/gewechat/downloader.py +0 -55
- astrbot/core/platform/sources/gewechat/gewechat_event.py +0 -255
- astrbot/core/platform/sources/gewechat/gewechat_platform_adapter.py +0 -103
- astrbot/core/platform/sources/gewechat/xml_data_parser.py +0 -110
- astrbot/core/provider/sources/dashscope_source.py +0 -203
- astrbot/core/provider/sources/dify_source.py +0 -281
- astrbot/core/provider/sources/llmtuner_source.py +0 -132
- astrbot/core/rag/embedding/openai_source.py +0 -20
- astrbot/core/rag/knowledge_db_mgr.py +0 -94
- astrbot/core/rag/store/__init__.py +0 -9
- astrbot/core/rag/store/chroma_db.py +0 -42
- astrbot/core/utils/dify_api_client.py +0 -152
- astrbot-3.5.6.dist-info/METADATA +0 -249
- astrbot-3.5.6.dist-info/RECORD +0 -158
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/entry_points.txt +0 -0
- {astrbot-3.5.6.dist-info → astrbot-4.7.0.dist-info}/licenses/LICENSE +0 -0
astrbot/dashboard/routes/chat.py
CHANGED
|
@@ -1,13 +1,28 @@
|
|
|
1
|
-
import
|
|
1
|
+
import asyncio
|
|
2
2
|
import json
|
|
3
3
|
import os
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
|
|
7
|
-
from
|
|
8
|
-
import
|
|
4
|
+
import uuid
|
|
5
|
+
from contextlib import asynccontextmanager
|
|
6
|
+
|
|
7
|
+
from quart import Response as QuartResponse
|
|
8
|
+
from quart import g, make_response, request
|
|
9
|
+
|
|
9
10
|
from astrbot.core import logger
|
|
10
11
|
from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
|
|
12
|
+
from astrbot.core.db import BaseDatabase
|
|
13
|
+
from astrbot.core.platform.sources.webchat.webchat_queue_mgr import webchat_queue_mgr
|
|
14
|
+
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
|
|
15
|
+
|
|
16
|
+
from .route import Response, Route, RouteContext
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
@asynccontextmanager
|
|
20
|
+
async def track_conversation(convs: dict, conv_id: str):
|
|
21
|
+
convs[conv_id] = True
|
|
22
|
+
try:
|
|
23
|
+
yield
|
|
24
|
+
finally:
|
|
25
|
+
convs.pop(conv_id, None)
|
|
11
26
|
|
|
12
27
|
|
|
13
28
|
class ChatRoute(Route):
|
|
@@ -20,38 +35,30 @@ class ChatRoute(Route):
|
|
|
20
35
|
super().__init__(context)
|
|
21
36
|
self.routes = {
|
|
22
37
|
"/chat/send": ("POST", self.chat),
|
|
23
|
-
"/chat/
|
|
24
|
-
"/chat/
|
|
25
|
-
"/chat/
|
|
26
|
-
"/chat/
|
|
27
|
-
"/chat/
|
|
38
|
+
"/chat/new_session": ("GET", self.new_session),
|
|
39
|
+
"/chat/sessions": ("GET", self.get_sessions),
|
|
40
|
+
"/chat/get_session": ("GET", self.get_session),
|
|
41
|
+
"/chat/delete_session": ("GET", self.delete_webchat_session),
|
|
42
|
+
"/chat/update_session_display_name": (
|
|
43
|
+
"POST",
|
|
44
|
+
self.update_session_display_name,
|
|
45
|
+
),
|
|
28
46
|
"/chat/get_file": ("GET", self.get_file),
|
|
29
47
|
"/chat/post_image": ("POST", self.post_image),
|
|
30
48
|
"/chat/post_file": ("POST", self.post_file),
|
|
31
|
-
"/chat/status": ("GET", self.status),
|
|
32
49
|
}
|
|
33
|
-
self.db = db
|
|
34
50
|
self.core_lifecycle = core_lifecycle
|
|
35
51
|
self.register_routes()
|
|
36
|
-
self.imgs_dir = "
|
|
52
|
+
self.imgs_dir = os.path.join(get_astrbot_data_path(), "webchat", "imgs")
|
|
53
|
+
os.makedirs(self.imgs_dir, exist_ok=True)
|
|
37
54
|
|
|
38
55
|
self.supported_imgs = ["jpg", "jpeg", "png", "gif", "webp"]
|
|
56
|
+
self.conv_mgr = core_lifecycle.conversation_manager
|
|
57
|
+
self.platform_history_mgr = core_lifecycle.platform_message_history_manager
|
|
58
|
+
self.db = db
|
|
59
|
+
self.umop_config_router = core_lifecycle.umop_config_router
|
|
39
60
|
|
|
40
|
-
self.
|
|
41
|
-
self.curr_chat_sse = {}
|
|
42
|
-
|
|
43
|
-
async def status(self):
|
|
44
|
-
has_llm_enabled = (
|
|
45
|
-
self.core_lifecycle.provider_manager.curr_provider_inst is not None
|
|
46
|
-
)
|
|
47
|
-
has_stt_enabled = (
|
|
48
|
-
self.core_lifecycle.provider_manager.curr_stt_provider_inst is not None
|
|
49
|
-
)
|
|
50
|
-
return (
|
|
51
|
-
Response()
|
|
52
|
-
.ok(data={"llm_enabled": has_llm_enabled, "stt_enabled": has_stt_enabled})
|
|
53
|
-
.__dict__
|
|
54
|
-
)
|
|
61
|
+
self.running_convs: dict[str, bool] = {}
|
|
55
62
|
|
|
56
63
|
async def get_file(self):
|
|
57
64
|
filename = request.args.get("filename")
|
|
@@ -59,16 +66,24 @@ class ChatRoute(Route):
|
|
|
59
66
|
return Response().error("Missing key: filename").__dict__
|
|
60
67
|
|
|
61
68
|
try:
|
|
62
|
-
|
|
63
|
-
|
|
69
|
+
file_path = os.path.join(self.imgs_dir, os.path.basename(filename))
|
|
70
|
+
real_file_path = os.path.realpath(file_path)
|
|
71
|
+
real_imgs_dir = os.path.realpath(self.imgs_dir)
|
|
72
|
+
|
|
73
|
+
if not real_file_path.startswith(real_imgs_dir):
|
|
74
|
+
return Response().error("Invalid file path").__dict__
|
|
75
|
+
|
|
76
|
+
with open(real_file_path, "rb") as f:
|
|
77
|
+
filename_ext = os.path.splitext(filename)[1].lower()
|
|
78
|
+
|
|
79
|
+
if filename_ext == ".wav":
|
|
64
80
|
return QuartResponse(f.read(), mimetype="audio/wav")
|
|
65
|
-
|
|
81
|
+
if filename_ext[1:] in self.supported_imgs:
|
|
66
82
|
return QuartResponse(f.read(), mimetype="image/jpeg")
|
|
67
|
-
|
|
68
|
-
return QuartResponse(f.read())
|
|
83
|
+
return QuartResponse(f.read())
|
|
69
84
|
|
|
70
|
-
except FileNotFoundError:
|
|
71
|
-
return Response().error("File
|
|
85
|
+
except (FileNotFoundError, OSError):
|
|
86
|
+
return Response().error("File access error").__dict__
|
|
72
87
|
|
|
73
88
|
async def post_image(self):
|
|
74
89
|
post_data = await request.files
|
|
@@ -88,8 +103,7 @@ class ChatRoute(Route):
|
|
|
88
103
|
return Response().error("Missing key: file").__dict__
|
|
89
104
|
|
|
90
105
|
file = post_data["file"]
|
|
91
|
-
filename = f"{
|
|
92
|
-
print(file)
|
|
106
|
+
filename = f"{uuid.uuid4()!s}"
|
|
93
107
|
# 通过文件格式判断文件类型
|
|
94
108
|
if file.content_type.startswith("audio"):
|
|
95
109
|
filename += ".wav"
|
|
@@ -106,112 +120,125 @@ class ChatRoute(Route):
|
|
|
106
120
|
if "message" not in post_data and "image_url" not in post_data:
|
|
107
121
|
return Response().error("Missing key: message or image_url").__dict__
|
|
108
122
|
|
|
109
|
-
if "conversation_id" not in post_data:
|
|
110
|
-
return
|
|
123
|
+
if "session_id" not in post_data and "conversation_id" not in post_data:
|
|
124
|
+
return (
|
|
125
|
+
Response().error("Missing key: session_id or conversation_id").__dict__
|
|
126
|
+
)
|
|
111
127
|
|
|
112
128
|
message = post_data["message"]
|
|
113
|
-
conversation_id = post_data["conversation_id"]
|
|
129
|
+
# conversation_id = post_data["conversation_id"]
|
|
130
|
+
session_id = post_data.get("session_id", post_data.get("conversation_id"))
|
|
114
131
|
image_url = post_data.get("image_url")
|
|
115
132
|
audio_url = post_data.get("audio_url")
|
|
133
|
+
selected_provider = post_data.get("selected_provider")
|
|
134
|
+
selected_model = post_data.get("selected_model")
|
|
135
|
+
enable_streaming = post_data.get("enable_streaming", True) # 默认为 True
|
|
136
|
+
|
|
116
137
|
if not message and not image_url and not audio_url:
|
|
117
138
|
return (
|
|
118
139
|
Response()
|
|
119
140
|
.error("Message and image_url and audio_url are empty")
|
|
120
141
|
.__dict__
|
|
121
142
|
)
|
|
122
|
-
if not
|
|
123
|
-
return Response().error("
|
|
143
|
+
if not session_id:
|
|
144
|
+
return Response().error("session_id is empty").__dict__
|
|
124
145
|
|
|
125
|
-
|
|
146
|
+
# 追加用户消息
|
|
147
|
+
webchat_conv_id = session_id
|
|
126
148
|
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
username,
|
|
130
|
-
conversation_id,
|
|
131
|
-
{
|
|
132
|
-
"message": message,
|
|
133
|
-
"image_url": image_url, # list
|
|
134
|
-
"audio_url": audio_url,
|
|
135
|
-
},
|
|
136
|
-
)
|
|
137
|
-
)
|
|
149
|
+
# 获取会话特定的队列
|
|
150
|
+
back_queue = webchat_queue_mgr.get_or_create_back_queue(webchat_conv_id)
|
|
138
151
|
|
|
139
|
-
# 持久化
|
|
140
|
-
conversation = self.db.get_conversation_by_user_id(username, conversation_id)
|
|
141
|
-
try:
|
|
142
|
-
history = json.loads(conversation.history)
|
|
143
|
-
except BaseException as e:
|
|
144
|
-
print(e)
|
|
145
|
-
history = []
|
|
146
152
|
new_his = {"type": "user", "message": message}
|
|
147
153
|
if image_url:
|
|
148
154
|
new_his["image_url"] = image_url
|
|
149
155
|
if audio_url:
|
|
150
156
|
new_his["audio_url"] = audio_url
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
157
|
+
await self.platform_history_mgr.insert(
|
|
158
|
+
platform_id="webchat",
|
|
159
|
+
user_id=webchat_conv_id,
|
|
160
|
+
content=new_his,
|
|
161
|
+
sender_id=username,
|
|
162
|
+
sender_name=username,
|
|
154
163
|
)
|
|
155
164
|
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
async def listener(self):
|
|
159
|
-
"""一直保持长连接"""
|
|
160
|
-
|
|
161
|
-
username = g.get("username", "guest")
|
|
162
|
-
|
|
163
|
-
if username in self.curr_chat_sse:
|
|
164
|
-
return Response().error("Already connected").__dict__
|
|
165
|
+
async def stream():
|
|
166
|
+
client_disconnected = False
|
|
165
167
|
|
|
166
|
-
|
|
168
|
+
try:
|
|
169
|
+
async with track_conversation(self.running_convs, webchat_conv_id):
|
|
170
|
+
while True:
|
|
171
|
+
try:
|
|
172
|
+
result = await asyncio.wait_for(back_queue.get(), timeout=1)
|
|
173
|
+
except asyncio.TimeoutError:
|
|
174
|
+
continue
|
|
175
|
+
except asyncio.CancelledError:
|
|
176
|
+
logger.debug(f"[WebChat] 用户 {username} 断开聊天长连接。")
|
|
177
|
+
client_disconnected = True
|
|
178
|
+
except Exception as e:
|
|
179
|
+
logger.error(f"WebChat stream error: {e}")
|
|
180
|
+
|
|
181
|
+
if not result:
|
|
182
|
+
continue
|
|
183
|
+
|
|
184
|
+
result_text = result["data"]
|
|
185
|
+
type = result.get("type")
|
|
186
|
+
streaming = result.get("streaming", False)
|
|
167
187
|
|
|
168
|
-
|
|
188
|
+
try:
|
|
189
|
+
if not client_disconnected:
|
|
190
|
+
yield f"data: {json.dumps(result, ensure_ascii=False)}\n\n"
|
|
191
|
+
except Exception as e:
|
|
192
|
+
if not client_disconnected:
|
|
193
|
+
logger.debug(
|
|
194
|
+
f"[WebChat] 用户 {username} 断开聊天长连接。 {e}",
|
|
195
|
+
)
|
|
196
|
+
client_disconnected = True
|
|
169
197
|
|
|
170
|
-
async def stream():
|
|
171
|
-
try:
|
|
172
|
-
yield f"data: {heartbeat}\n\n" # 心跳包
|
|
173
|
-
while True:
|
|
174
|
-
try:
|
|
175
|
-
result = await asyncio.wait_for(
|
|
176
|
-
web_chat_back_queue.get(), timeout=10
|
|
177
|
-
) # 设置超时时间为5秒
|
|
178
|
-
except asyncio.TimeoutError:
|
|
179
|
-
yield f"data: {heartbeat}\n\n" # 心跳包
|
|
180
|
-
continue
|
|
181
|
-
|
|
182
|
-
if not result:
|
|
183
|
-
continue
|
|
184
|
-
|
|
185
|
-
result_text = result["data"]
|
|
186
|
-
type = result.get("type")
|
|
187
|
-
cid = result.get("cid")
|
|
188
|
-
streaming = result.get("streaming", False)
|
|
189
|
-
if cid != self.curr_user_cid.get(username):
|
|
190
|
-
# 丢弃
|
|
191
|
-
continue
|
|
192
|
-
yield f"data: {json.dumps(result, ensure_ascii=False)}\n\n"
|
|
193
|
-
await asyncio.sleep(0.05)
|
|
194
|
-
|
|
195
|
-
if streaming and type != "end":
|
|
196
|
-
continue
|
|
197
|
-
|
|
198
|
-
if result_text:
|
|
199
|
-
conversation = self.db.get_conversation_by_user_id(
|
|
200
|
-
username, cid
|
|
201
|
-
)
|
|
202
198
|
try:
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
199
|
+
if not client_disconnected:
|
|
200
|
+
await asyncio.sleep(0.05)
|
|
201
|
+
except asyncio.CancelledError:
|
|
202
|
+
logger.debug(f"[WebChat] 用户 {username} 断开聊天长连接。")
|
|
203
|
+
client_disconnected = True
|
|
204
|
+
|
|
205
|
+
if type == "end":
|
|
206
|
+
break
|
|
207
|
+
elif (
|
|
208
|
+
(streaming and type == "complete")
|
|
209
|
+
or not streaming
|
|
210
|
+
or type == "break"
|
|
211
|
+
):
|
|
212
|
+
# 追加机器人消息
|
|
213
|
+
new_his = {"type": "bot", "message": result_text}
|
|
214
|
+
if "reasoning" in result:
|
|
215
|
+
new_his["reasoning"] = result["reasoning"]
|
|
216
|
+
await self.platform_history_mgr.insert(
|
|
217
|
+
platform_id="webchat",
|
|
218
|
+
user_id=webchat_conv_id,
|
|
219
|
+
content=new_his,
|
|
220
|
+
sender_id="bot",
|
|
221
|
+
sender_name="bot",
|
|
222
|
+
)
|
|
223
|
+
except BaseException as e:
|
|
224
|
+
logger.exception(f"WebChat stream unexpected error: {e}", exc_info=True)
|
|
225
|
+
|
|
226
|
+
# 将消息放入会话特定的队列
|
|
227
|
+
chat_queue = webchat_queue_mgr.get_or_create_queue(webchat_conv_id)
|
|
228
|
+
await chat_queue.put(
|
|
229
|
+
(
|
|
230
|
+
username,
|
|
231
|
+
webchat_conv_id,
|
|
232
|
+
{
|
|
233
|
+
"message": message,
|
|
234
|
+
"image_url": image_url, # list
|
|
235
|
+
"audio_url": audio_url,
|
|
236
|
+
"selected_provider": selected_provider,
|
|
237
|
+
"selected_model": selected_model,
|
|
238
|
+
"enable_streaming": enable_streaming,
|
|
239
|
+
},
|
|
240
|
+
),
|
|
241
|
+
)
|
|
215
242
|
|
|
216
243
|
response = await make_response(
|
|
217
244
|
stream(),
|
|
@@ -222,37 +249,166 @@ class ChatRoute(Route):
|
|
|
222
249
|
"Connection": "keep-alive",
|
|
223
250
|
},
|
|
224
251
|
)
|
|
225
|
-
response.timeout = None
|
|
252
|
+
response.timeout = None # fix SSE auto disconnect issue
|
|
226
253
|
return response
|
|
227
254
|
|
|
228
|
-
async def
|
|
255
|
+
async def delete_webchat_session(self):
|
|
256
|
+
"""Delete a Platform session and all its related data."""
|
|
257
|
+
session_id = request.args.get("session_id")
|
|
258
|
+
if not session_id:
|
|
259
|
+
return Response().error("Missing key: session_id").__dict__
|
|
229
260
|
username = g.get("username", "guest")
|
|
230
|
-
conversation_id = request.args.get("conversation_id")
|
|
231
|
-
if not conversation_id:
|
|
232
|
-
return Response().error("Missing key: conversation_id").__dict__
|
|
233
261
|
|
|
234
|
-
|
|
262
|
+
# 验证会话是否存在且属于当前用户
|
|
263
|
+
session = await self.db.get_platform_session_by_id(session_id)
|
|
264
|
+
if not session:
|
|
265
|
+
return Response().error(f"Session {session_id} not found").__dict__
|
|
266
|
+
if session.creator != username:
|
|
267
|
+
return Response().error("Permission denied").__dict__
|
|
268
|
+
|
|
269
|
+
# 删除该会话下的所有对话
|
|
270
|
+
message_type = "GroupMessage" if session.is_group else "FriendMessage"
|
|
271
|
+
unified_msg_origin = f"{session.platform_id}:{message_type}:{session.platform_id}!{username}!{session_id}"
|
|
272
|
+
await self.conv_mgr.delete_conversations_by_user_id(unified_msg_origin)
|
|
273
|
+
|
|
274
|
+
# 删除消息历史
|
|
275
|
+
await self.platform_history_mgr.delete(
|
|
276
|
+
platform_id=session.platform_id,
|
|
277
|
+
user_id=session_id,
|
|
278
|
+
offset_sec=99999999,
|
|
279
|
+
)
|
|
280
|
+
|
|
281
|
+
# 删除与会话关联的配置路由
|
|
282
|
+
try:
|
|
283
|
+
await self.umop_config_router.delete_route(unified_msg_origin)
|
|
284
|
+
except ValueError as exc:
|
|
285
|
+
logger.warning(
|
|
286
|
+
"Failed to delete UMO route %s during session cleanup: %s",
|
|
287
|
+
unified_msg_origin,
|
|
288
|
+
exc,
|
|
289
|
+
)
|
|
290
|
+
|
|
291
|
+
# 清理队列(仅对 webchat)
|
|
292
|
+
if session.platform_id == "webchat":
|
|
293
|
+
webchat_queue_mgr.remove_queues(session_id)
|
|
294
|
+
|
|
295
|
+
# 删除会话
|
|
296
|
+
await self.db.delete_platform_session(session_id)
|
|
297
|
+
|
|
235
298
|
return Response().ok().__dict__
|
|
236
299
|
|
|
237
|
-
async def
|
|
300
|
+
async def new_session(self):
|
|
301
|
+
"""Create a new Platform session (default: webchat)."""
|
|
238
302
|
username = g.get("username", "guest")
|
|
239
|
-
conversation_id = str(uuid.uuid4())
|
|
240
|
-
self.db.new_conversation(username, conversation_id)
|
|
241
|
-
return Response().ok(data={"conversation_id": conversation_id}).__dict__
|
|
242
303
|
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
304
|
+
# 获取可选的 platform_id 参数,默认为 webchat
|
|
305
|
+
platform_id = request.args.get("platform_id", "webchat")
|
|
306
|
+
|
|
307
|
+
# 创建新会话
|
|
308
|
+
session = await self.db.create_platform_session(
|
|
309
|
+
creator=username,
|
|
310
|
+
platform_id=platform_id,
|
|
311
|
+
is_group=0,
|
|
312
|
+
)
|
|
313
|
+
|
|
314
|
+
return (
|
|
315
|
+
Response()
|
|
316
|
+
.ok(
|
|
317
|
+
data={
|
|
318
|
+
"session_id": session.session_id,
|
|
319
|
+
"platform_id": session.platform_id,
|
|
320
|
+
}
|
|
321
|
+
)
|
|
322
|
+
.__dict__
|
|
323
|
+
)
|
|
247
324
|
|
|
248
|
-
async def
|
|
325
|
+
async def get_sessions(self):
|
|
326
|
+
"""Get all Platform sessions for the current user."""
|
|
249
327
|
username = g.get("username", "guest")
|
|
250
|
-
conversation_id = request.args.get("conversation_id")
|
|
251
|
-
if not conversation_id:
|
|
252
|
-
return Response().error("Missing key: conversation_id").__dict__
|
|
253
328
|
|
|
254
|
-
|
|
329
|
+
# 获取可选的 platform_id 参数
|
|
330
|
+
platform_id = request.args.get("platform_id")
|
|
331
|
+
|
|
332
|
+
sessions = await self.db.get_platform_sessions_by_creator(
|
|
333
|
+
creator=username,
|
|
334
|
+
platform_id=platform_id,
|
|
335
|
+
page=1,
|
|
336
|
+
page_size=100, # 暂时返回前100个
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
# 转换为字典格式,并添加额外信息
|
|
340
|
+
sessions_data = []
|
|
341
|
+
for session in sessions:
|
|
342
|
+
sessions_data.append(
|
|
343
|
+
{
|
|
344
|
+
"session_id": session.session_id,
|
|
345
|
+
"platform_id": session.platform_id,
|
|
346
|
+
"creator": session.creator,
|
|
347
|
+
"display_name": session.display_name,
|
|
348
|
+
"is_group": session.is_group,
|
|
349
|
+
"created_at": session.created_at.astimezone().isoformat(),
|
|
350
|
+
"updated_at": session.updated_at.astimezone().isoformat(),
|
|
351
|
+
}
|
|
352
|
+
)
|
|
353
|
+
|
|
354
|
+
return Response().ok(data=sessions_data).__dict__
|
|
355
|
+
|
|
356
|
+
async def get_session(self):
|
|
357
|
+
"""Get session information and message history by session_id."""
|
|
358
|
+
session_id = request.args.get("session_id")
|
|
359
|
+
if not session_id:
|
|
360
|
+
return Response().error("Missing key: session_id").__dict__
|
|
361
|
+
|
|
362
|
+
# 获取会话信息以确定 platform_id
|
|
363
|
+
session = await self.db.get_platform_session_by_id(session_id)
|
|
364
|
+
platform_id = session.platform_id if session else "webchat"
|
|
365
|
+
|
|
366
|
+
# Get platform message history using session_id
|
|
367
|
+
history_ls = await self.platform_history_mgr.get(
|
|
368
|
+
platform_id=platform_id,
|
|
369
|
+
user_id=session_id,
|
|
370
|
+
page=1,
|
|
371
|
+
page_size=1000,
|
|
372
|
+
)
|
|
373
|
+
|
|
374
|
+
history_res = [history.model_dump() for history in history_ls]
|
|
375
|
+
|
|
376
|
+
return (
|
|
377
|
+
Response()
|
|
378
|
+
.ok(
|
|
379
|
+
data={
|
|
380
|
+
"history": history_res,
|
|
381
|
+
"is_running": self.running_convs.get(session_id, False),
|
|
382
|
+
},
|
|
383
|
+
)
|
|
384
|
+
.__dict__
|
|
385
|
+
)
|
|
386
|
+
|
|
387
|
+
async def update_session_display_name(self):
|
|
388
|
+
"""Update a Platform session's display name."""
|
|
389
|
+
post_data = await request.json
|
|
390
|
+
|
|
391
|
+
session_id = post_data.get("session_id")
|
|
392
|
+
display_name = post_data.get("display_name")
|
|
393
|
+
|
|
394
|
+
if not session_id:
|
|
395
|
+
return Response().error("Missing key: session_id").__dict__
|
|
396
|
+
if display_name is None:
|
|
397
|
+
return Response().error("Missing key: display_name").__dict__
|
|
398
|
+
|
|
399
|
+
username = g.get("username", "guest")
|
|
255
400
|
|
|
256
|
-
|
|
401
|
+
# 验证会话是否存在且属于当前用户
|
|
402
|
+
session = await self.db.get_platform_session_by_id(session_id)
|
|
403
|
+
if not session:
|
|
404
|
+
return Response().error(f"Session {session_id} not found").__dict__
|
|
405
|
+
if session.creator != username:
|
|
406
|
+
return Response().error("Permission denied").__dict__
|
|
407
|
+
|
|
408
|
+
# 更新 display_name
|
|
409
|
+
await self.db.update_platform_session(
|
|
410
|
+
session_id=session_id,
|
|
411
|
+
display_name=display_name,
|
|
412
|
+
)
|
|
257
413
|
|
|
258
|
-
return Response().ok(
|
|
414
|
+
return Response().ok().__dict__
|