AstrBot 4.7.4__py3-none-any.whl → 4.9.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/cli/__init__.py +1 -1
- astrbot/core/agent/runners/tool_loop_agent_runner.py +0 -1
- astrbot/core/agent/tool.py +7 -2
- astrbot/core/astr_agent_run_util.py +15 -1
- astrbot/core/astr_agent_tool_exec.py +5 -1
- astrbot/core/config/astrbot_config.py +4 -0
- astrbot/core/config/default.py +116 -1
- astrbot/core/core_lifecycle.py +1 -1
- astrbot/core/db/__init__.py +32 -4
- astrbot/core/db/migration/migra_3_to_4.py +2 -0
- astrbot/core/db/migration/sqlite_v3.py +6 -4
- astrbot/core/db/po.py +16 -15
- astrbot/core/db/sqlite.py +56 -1
- astrbot/core/db/vec_db/faiss_impl/embedding_storage.py +2 -0
- astrbot/core/event_bus.py +6 -1
- astrbot/core/knowledge_base/retrieval/manager.py +5 -1
- astrbot/core/log.py +2 -1
- astrbot/core/message/components.py +9 -3
- astrbot/core/persona_mgr.py +2 -2
- astrbot/core/pipeline/content_safety_check/stage.py +1 -1
- astrbot/core/pipeline/context_utils.py +2 -1
- astrbot/core/pipeline/process_stage/method/agent_sub_stages/third_party.py +1 -1
- astrbot/core/pipeline/process_stage/method/star_request.py +1 -2
- astrbot/core/pipeline/process_stage/stage.py +1 -1
- astrbot/core/pipeline/respond/stage.py +4 -2
- astrbot/core/pipeline/result_decorate/stage.py +68 -21
- astrbot/core/pipeline/scheduler.py +5 -1
- astrbot/core/pipeline/waking_check/stage.py +10 -0
- astrbot/core/platform/astr_message_event.py +5 -3
- astrbot/core/platform/astrbot_message.py +2 -2
- astrbot/core/platform/manager.py +71 -9
- astrbot/core/platform/platform.py +109 -4
- astrbot/core/platform/platform_metadata.py +1 -1
- astrbot/core/platform/register.py +1 -0
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py +8 -6
- astrbot/core/platform/sources/aiocqhttp/aiocqhttp_platform_adapter.py +13 -8
- astrbot/core/platform/sources/dingtalk/dingtalk_adapter.py +28 -22
- astrbot/core/platform/sources/dingtalk/dingtalk_event.py +5 -2
- astrbot/core/platform/sources/discord/client.py +16 -4
- astrbot/core/platform/sources/discord/components.py +2 -2
- astrbot/core/platform/sources/discord/discord_platform_adapter.py +53 -26
- astrbot/core/platform/sources/discord/discord_platform_event.py +29 -8
- astrbot/core/platform/sources/lark/lark_adapter.py +178 -22
- astrbot/core/platform/sources/lark/lark_event.py +39 -4
- astrbot/core/platform/sources/lark/server.py +206 -0
- astrbot/core/platform/sources/misskey/misskey_adapter.py +3 -5
- astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py +64 -18
- astrbot/core/platform/sources/qqofficial/qqofficial_platform_adapter.py +14 -10
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_adapter.py +36 -11
- astrbot/core/platform/sources/qqofficial_webhook/qo_webhook_server.py +15 -2
- astrbot/core/platform/sources/satori/satori_adapter.py +1 -2
- astrbot/core/platform/sources/slack/client.py +58 -40
- astrbot/core/platform/sources/slack/slack_adapter.py +36 -16
- astrbot/core/platform/sources/slack/slack_event.py +11 -10
- astrbot/core/platform/sources/telegram/tg_adapter.py +2 -3
- astrbot/core/platform/sources/telegram/tg_event.py +23 -27
- astrbot/core/platform/sources/webchat/webchat_adapter.py +97 -31
- astrbot/core/platform/sources/webchat/webchat_event.py +35 -35
- astrbot/core/platform/sources/wechatpadpro/wechatpadpro_adapter.py +27 -11
- astrbot/core/platform/sources/wecom/wecom_adapter.py +75 -36
- astrbot/core/platform/sources/wecom/wecom_event.py +3 -3
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_adapter.py +26 -9
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_event.py +3 -3
- astrbot/core/platform/sources/wecom_ai_bot/wecomai_server.py +27 -5
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_adapter.py +81 -35
- astrbot/core/platform/sources/weixin_official_account/weixin_offacc_event.py +11 -8
- astrbot/core/platform_message_history_mgr.py +3 -3
- astrbot/core/provider/func_tool_manager.py +3 -3
- astrbot/core/provider/manager.py +130 -74
- astrbot/core/provider/provider.py +12 -1
- astrbot/core/provider/sources/azure_tts_source.py +31 -9
- astrbot/core/provider/sources/bailian_rerank_source.py +4 -0
- astrbot/core/provider/sources/dashscope_tts.py +3 -2
- astrbot/core/provider/sources/edge_tts_source.py +1 -1
- astrbot/core/provider/sources/fishaudio_tts_api_source.py +5 -4
- astrbot/core/provider/sources/gemini_embedding_source.py +15 -5
- astrbot/core/provider/sources/gemini_source.py +12 -10
- astrbot/core/provider/sources/minimax_tts_api_source.py +4 -2
- astrbot/core/provider/sources/openai_embedding_source.py +2 -2
- astrbot/core/provider/sources/openai_source.py +4 -0
- astrbot/core/provider/sources/sensevoice_selfhosted_source.py +5 -2
- astrbot/core/provider/sources/vllm_rerank_source.py +1 -0
- astrbot/core/provider/sources/whisper_api_source.py +44 -12
- astrbot/core/provider/sources/whisper_selfhosted_source.py +6 -2
- astrbot/core/provider/sources/xinference_rerank_source.py +10 -2
- astrbot/core/star/context.py +2 -2
- astrbot/core/star/register/star_handler.py +22 -5
- astrbot/core/star/star_handler.py +85 -4
- astrbot/core/updator.py +3 -3
- astrbot/core/utils/io.py +1 -1
- astrbot/core/utils/session_waiter.py +17 -10
- astrbot/core/utils/shared_preferences.py +32 -0
- astrbot/core/utils/t2i/__init__.py +2 -2
- astrbot/core/utils/t2i/local_strategy.py +25 -31
- astrbot/core/utils/tencent_record_helper.py +2 -2
- astrbot/core/utils/version_comparator.py +6 -3
- astrbot/core/utils/webhook_utils.py +66 -0
- astrbot/dashboard/routes/__init__.py +2 -0
- astrbot/dashboard/routes/chat.py +311 -76
- astrbot/dashboard/routes/config.py +14 -5
- astrbot/dashboard/routes/knowledge_base.py +254 -79
- astrbot/dashboard/routes/log.py +13 -8
- astrbot/dashboard/routes/platform.py +100 -0
- astrbot/dashboard/routes/plugin.py +108 -51
- astrbot/dashboard/routes/route.py +2 -0
- astrbot/dashboard/server.py +9 -4
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/METADATA +50 -37
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/RECORD +111 -108
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/WHEEL +0 -0
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/entry_points.txt +0 -0
- {astrbot-4.7.4.dist-info → astrbot-4.9.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -48,6 +48,7 @@ class KnowledgeBaseRoute(Route):
|
|
|
48
48
|
# 文档管理
|
|
49
49
|
"/kb/document/list": ("GET", self.list_documents),
|
|
50
50
|
"/kb/document/upload": ("POST", self.upload_document),
|
|
51
|
+
"/kb/document/import": ("POST", self.import_documents),
|
|
51
52
|
"/kb/document/upload/url": ("POST", self.upload_document_from_url),
|
|
52
53
|
"/kb/document/upload/progress": ("GET", self.get_upload_progress),
|
|
53
54
|
"/kb/document/get": ("GET", self.get_document),
|
|
@@ -66,6 +67,65 @@ class KnowledgeBaseRoute(Route):
|
|
|
66
67
|
def _get_kb_manager(self):
|
|
67
68
|
return self.core_lifecycle.kb_manager
|
|
68
69
|
|
|
70
|
+
def _init_task(self, task_id: str, status: str = "pending") -> None:
|
|
71
|
+
self.upload_tasks[task_id] = {
|
|
72
|
+
"status": status,
|
|
73
|
+
"result": None,
|
|
74
|
+
"error": None,
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
def _set_task_result(
|
|
78
|
+
self, task_id: str, status: str, result: any = None, error: str | None = None
|
|
79
|
+
) -> None:
|
|
80
|
+
self.upload_tasks[task_id] = {
|
|
81
|
+
"status": status,
|
|
82
|
+
"result": result,
|
|
83
|
+
"error": error,
|
|
84
|
+
}
|
|
85
|
+
if task_id in self.upload_progress:
|
|
86
|
+
self.upload_progress[task_id]["status"] = status
|
|
87
|
+
|
|
88
|
+
def _update_progress(
|
|
89
|
+
self,
|
|
90
|
+
task_id: str,
|
|
91
|
+
*,
|
|
92
|
+
status: str | None = None,
|
|
93
|
+
file_index: int | None = None,
|
|
94
|
+
file_name: str | None = None,
|
|
95
|
+
stage: str | None = None,
|
|
96
|
+
current: int | None = None,
|
|
97
|
+
total: int | None = None,
|
|
98
|
+
) -> None:
|
|
99
|
+
if task_id not in self.upload_progress:
|
|
100
|
+
return
|
|
101
|
+
p = self.upload_progress[task_id]
|
|
102
|
+
if status is not None:
|
|
103
|
+
p["status"] = status
|
|
104
|
+
if file_index is not None:
|
|
105
|
+
p["file_index"] = file_index
|
|
106
|
+
if file_name is not None:
|
|
107
|
+
p["file_name"] = file_name
|
|
108
|
+
if stage is not None:
|
|
109
|
+
p["stage"] = stage
|
|
110
|
+
if current is not None:
|
|
111
|
+
p["current"] = current
|
|
112
|
+
if total is not None:
|
|
113
|
+
p["total"] = total
|
|
114
|
+
|
|
115
|
+
def _make_progress_callback(self, task_id: str, file_idx: int, file_name: str):
|
|
116
|
+
async def _callback(stage: str, current: int, total: int):
|
|
117
|
+
self._update_progress(
|
|
118
|
+
task_id,
|
|
119
|
+
status="processing",
|
|
120
|
+
file_index=file_idx,
|
|
121
|
+
file_name=file_name,
|
|
122
|
+
stage=stage,
|
|
123
|
+
current=current,
|
|
124
|
+
total=total,
|
|
125
|
+
)
|
|
126
|
+
|
|
127
|
+
return _callback
|
|
128
|
+
|
|
69
129
|
async def _background_upload_task(
|
|
70
130
|
self,
|
|
71
131
|
task_id: str,
|
|
@@ -80,11 +140,7 @@ class KnowledgeBaseRoute(Route):
|
|
|
80
140
|
"""后台上传任务"""
|
|
81
141
|
try:
|
|
82
142
|
# 初始化任务状态
|
|
83
|
-
self.
|
|
84
|
-
"status": "processing",
|
|
85
|
-
"result": None,
|
|
86
|
-
"error": None,
|
|
87
|
-
}
|
|
143
|
+
self._init_task(task_id, status="processing")
|
|
88
144
|
self.upload_progress[task_id] = {
|
|
89
145
|
"status": "processing",
|
|
90
146
|
"file_index": 0,
|
|
@@ -100,30 +156,20 @@ class KnowledgeBaseRoute(Route):
|
|
|
100
156
|
for file_idx, file_info in enumerate(files_to_upload):
|
|
101
157
|
try:
|
|
102
158
|
# 更新整体进度
|
|
103
|
-
self.
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
},
|
|
159
|
+
self._update_progress(
|
|
160
|
+
task_id,
|
|
161
|
+
status="processing",
|
|
162
|
+
file_index=file_idx,
|
|
163
|
+
file_name=file_info["file_name"],
|
|
164
|
+
stage="parsing",
|
|
165
|
+
current=0,
|
|
166
|
+
total=100,
|
|
112
167
|
)
|
|
113
168
|
|
|
114
169
|
# 创建进度回调函数
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
{
|
|
119
|
-
"status": "processing",
|
|
120
|
-
"file_index": file_idx,
|
|
121
|
-
"file_name": file_info["file_name"],
|
|
122
|
-
"stage": stage,
|
|
123
|
-
"current": current,
|
|
124
|
-
"total": total,
|
|
125
|
-
},
|
|
126
|
-
)
|
|
170
|
+
progress_callback = self._make_progress_callback(
|
|
171
|
+
task_id, file_idx, file_info["file_name"]
|
|
172
|
+
)
|
|
127
173
|
|
|
128
174
|
doc = await kb_helper.upload_document(
|
|
129
175
|
file_name=file_info["file_name"],
|
|
@@ -154,23 +200,99 @@ class KnowledgeBaseRoute(Route):
|
|
|
154
200
|
"failed_count": len(failed_docs),
|
|
155
201
|
}
|
|
156
202
|
|
|
157
|
-
self.
|
|
158
|
-
"status": "completed",
|
|
159
|
-
"result": result,
|
|
160
|
-
"error": None,
|
|
161
|
-
}
|
|
162
|
-
self.upload_progress[task_id]["status"] = "completed"
|
|
203
|
+
self._set_task_result(task_id, "completed", result=result)
|
|
163
204
|
|
|
164
205
|
except Exception as e:
|
|
165
206
|
logger.error(f"后台上传任务 {task_id} 失败: {e}")
|
|
166
207
|
logger.error(traceback.format_exc())
|
|
167
|
-
self.
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
208
|
+
self._set_task_result(task_id, "failed", error=str(e))
|
|
209
|
+
|
|
210
|
+
async def _background_import_task(
|
|
211
|
+
self,
|
|
212
|
+
task_id: str,
|
|
213
|
+
kb_helper,
|
|
214
|
+
documents: list,
|
|
215
|
+
batch_size: int,
|
|
216
|
+
tasks_limit: int,
|
|
217
|
+
max_retries: int,
|
|
218
|
+
):
|
|
219
|
+
"""后台导入预切片文档任务"""
|
|
220
|
+
try:
|
|
221
|
+
# 初始化任务状态
|
|
222
|
+
self._init_task(task_id, status="processing")
|
|
223
|
+
self.upload_progress[task_id] = {
|
|
224
|
+
"status": "processing",
|
|
225
|
+
"file_index": 0,
|
|
226
|
+
"file_total": len(documents),
|
|
227
|
+
"stage": "waiting",
|
|
228
|
+
"current": 0,
|
|
229
|
+
"total": 100,
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
uploaded_docs = []
|
|
233
|
+
failed_docs = []
|
|
234
|
+
|
|
235
|
+
for file_idx, doc_info in enumerate(documents):
|
|
236
|
+
file_name = doc_info.get("file_name", f"imported_doc_{file_idx}")
|
|
237
|
+
chunks = doc_info.get("chunks", [])
|
|
238
|
+
|
|
239
|
+
try:
|
|
240
|
+
# 更新整体进度
|
|
241
|
+
self._update_progress(
|
|
242
|
+
task_id,
|
|
243
|
+
status="processing",
|
|
244
|
+
file_index=file_idx,
|
|
245
|
+
file_name=file_name,
|
|
246
|
+
stage="importing",
|
|
247
|
+
current=0,
|
|
248
|
+
total=100,
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# 创建进度回调函数
|
|
252
|
+
progress_callback = self._make_progress_callback(
|
|
253
|
+
task_id, file_idx, file_name
|
|
254
|
+
)
|
|
255
|
+
|
|
256
|
+
# 调用 upload_document,传入 pre_chunked_text
|
|
257
|
+
doc = await kb_helper.upload_document(
|
|
258
|
+
file_name=file_name,
|
|
259
|
+
file_content=None, # 预切片模式下不需要原始内容
|
|
260
|
+
file_type=doc_info.get("file_type")
|
|
261
|
+
or (
|
|
262
|
+
file_name.rsplit(".", 1)[-1].lower()
|
|
263
|
+
if "." in file_name
|
|
264
|
+
else "txt"
|
|
265
|
+
),
|
|
266
|
+
batch_size=batch_size,
|
|
267
|
+
tasks_limit=tasks_limit,
|
|
268
|
+
max_retries=max_retries,
|
|
269
|
+
progress_callback=progress_callback,
|
|
270
|
+
pre_chunked_text=chunks,
|
|
271
|
+
)
|
|
272
|
+
|
|
273
|
+
uploaded_docs.append(doc.model_dump())
|
|
274
|
+
except Exception as e:
|
|
275
|
+
logger.error(f"导入文档 {file_name} 失败: {e}")
|
|
276
|
+
failed_docs.append(
|
|
277
|
+
{"file_name": file_name, "error": str(e)},
|
|
278
|
+
)
|
|
279
|
+
|
|
280
|
+
# 更新任务完成状态
|
|
281
|
+
result = {
|
|
282
|
+
"task_id": task_id,
|
|
283
|
+
"uploaded": uploaded_docs,
|
|
284
|
+
"failed": failed_docs,
|
|
285
|
+
"total": len(documents),
|
|
286
|
+
"success_count": len(uploaded_docs),
|
|
287
|
+
"failed_count": len(failed_docs),
|
|
171
288
|
}
|
|
172
|
-
|
|
173
|
-
|
|
289
|
+
|
|
290
|
+
self._set_task_result(task_id, "completed", result=result)
|
|
291
|
+
|
|
292
|
+
except Exception as e:
|
|
293
|
+
logger.error(f"后台导入任务 {task_id} 失败: {e}")
|
|
294
|
+
logger.error(traceback.format_exc())
|
|
295
|
+
self._set_task_result(task_id, "failed", error=str(e))
|
|
174
296
|
|
|
175
297
|
async def list_kbs(self):
|
|
176
298
|
"""获取知识库列表
|
|
@@ -274,7 +396,7 @@ class KnowledgeBaseRoute(Route):
|
|
|
274
396
|
except Exception as e:
|
|
275
397
|
return (
|
|
276
398
|
Response()
|
|
277
|
-
.error(f"测试重排序模型失败: {e!s}
|
|
399
|
+
.error(f"测试重排序模型失败: {e!s},请检查平台日志输出。")
|
|
278
400
|
.__dict__
|
|
279
401
|
)
|
|
280
402
|
|
|
@@ -614,11 +736,7 @@ class KnowledgeBaseRoute(Route):
|
|
|
614
736
|
task_id = str(uuid.uuid4())
|
|
615
737
|
|
|
616
738
|
# 初始化任务状态
|
|
617
|
-
self.
|
|
618
|
-
"status": "pending",
|
|
619
|
-
"result": None,
|
|
620
|
-
"error": None,
|
|
621
|
-
}
|
|
739
|
+
self._init_task(task_id, status="pending")
|
|
622
740
|
|
|
623
741
|
# 启动后台任务
|
|
624
742
|
asyncio.create_task(
|
|
@@ -653,6 +771,93 @@ class KnowledgeBaseRoute(Route):
|
|
|
653
771
|
logger.error(traceback.format_exc())
|
|
654
772
|
return Response().error(f"上传文档失败: {e!s}").__dict__
|
|
655
773
|
|
|
774
|
+
def _validate_import_request(self, data: dict):
|
|
775
|
+
kb_id = data.get("kb_id")
|
|
776
|
+
if not kb_id:
|
|
777
|
+
raise ValueError("缺少参数 kb_id")
|
|
778
|
+
|
|
779
|
+
documents = data.get("documents")
|
|
780
|
+
if not documents or not isinstance(documents, list):
|
|
781
|
+
raise ValueError("缺少参数 documents 或格式错误")
|
|
782
|
+
|
|
783
|
+
for doc in documents:
|
|
784
|
+
if "file_name" not in doc or "chunks" not in doc:
|
|
785
|
+
raise ValueError("文档格式错误,必须包含 file_name 和 chunks")
|
|
786
|
+
if not isinstance(doc["chunks"], list):
|
|
787
|
+
raise ValueError("chunks 必须是列表")
|
|
788
|
+
if not all(
|
|
789
|
+
isinstance(chunk, str) and chunk.strip() for chunk in doc["chunks"]
|
|
790
|
+
):
|
|
791
|
+
raise ValueError("chunks 必须是非空字符串列表")
|
|
792
|
+
|
|
793
|
+
batch_size = data.get("batch_size", 32)
|
|
794
|
+
tasks_limit = data.get("tasks_limit", 3)
|
|
795
|
+
max_retries = data.get("max_retries", 3)
|
|
796
|
+
return kb_id, documents, batch_size, tasks_limit, max_retries
|
|
797
|
+
|
|
798
|
+
async def import_documents(self):
|
|
799
|
+
"""导入预切片文档
|
|
800
|
+
|
|
801
|
+
Body:
|
|
802
|
+
- kb_id: 知识库 ID (必填)
|
|
803
|
+
- documents: 文档列表 (必填)
|
|
804
|
+
- file_name: 文件名 (必填)
|
|
805
|
+
- chunks: 切片列表 (必填, list[str])
|
|
806
|
+
- file_type: 文件类型 (可选, 默认从文件名推断或为 txt)
|
|
807
|
+
- batch_size: 批处理大小 (可选, 默认32)
|
|
808
|
+
- tasks_limit: 并发任务限制 (可选, 默认3)
|
|
809
|
+
- max_retries: 最大重试次数 (可选, 默认3)
|
|
810
|
+
"""
|
|
811
|
+
try:
|
|
812
|
+
kb_manager = self._get_kb_manager()
|
|
813
|
+
data = await request.json
|
|
814
|
+
|
|
815
|
+
kb_id, documents, batch_size, tasks_limit, max_retries = (
|
|
816
|
+
self._validate_import_request(data)
|
|
817
|
+
)
|
|
818
|
+
|
|
819
|
+
# 获取知识库
|
|
820
|
+
kb_helper = await kb_manager.get_kb(kb_id)
|
|
821
|
+
if not kb_helper:
|
|
822
|
+
return Response().error("知识库不存在").__dict__
|
|
823
|
+
|
|
824
|
+
# 生成任务ID
|
|
825
|
+
task_id = str(uuid.uuid4())
|
|
826
|
+
|
|
827
|
+
# 初始化任务状态
|
|
828
|
+
self._init_task(task_id, status="pending")
|
|
829
|
+
|
|
830
|
+
# 启动后台任务
|
|
831
|
+
asyncio.create_task(
|
|
832
|
+
self._background_import_task(
|
|
833
|
+
task_id=task_id,
|
|
834
|
+
kb_helper=kb_helper,
|
|
835
|
+
documents=documents,
|
|
836
|
+
batch_size=batch_size,
|
|
837
|
+
tasks_limit=tasks_limit,
|
|
838
|
+
max_retries=max_retries,
|
|
839
|
+
),
|
|
840
|
+
)
|
|
841
|
+
|
|
842
|
+
return (
|
|
843
|
+
Response()
|
|
844
|
+
.ok(
|
|
845
|
+
{
|
|
846
|
+
"task_id": task_id,
|
|
847
|
+
"doc_count": len(documents),
|
|
848
|
+
"message": "import task created, processing in background",
|
|
849
|
+
},
|
|
850
|
+
)
|
|
851
|
+
.__dict__
|
|
852
|
+
)
|
|
853
|
+
|
|
854
|
+
except ValueError as e:
|
|
855
|
+
return Response().error(str(e)).__dict__
|
|
856
|
+
except Exception as e:
|
|
857
|
+
logger.error(f"导入文档失败: {e}")
|
|
858
|
+
logger.error(traceback.format_exc())
|
|
859
|
+
return Response().error(f"导入文档失败: {e!s}").__dict__
|
|
860
|
+
|
|
656
861
|
async def get_upload_progress(self):
|
|
657
862
|
"""获取上传进度和结果
|
|
658
863
|
|
|
@@ -960,11 +1165,7 @@ class KnowledgeBaseRoute(Route):
|
|
|
960
1165
|
task_id = str(uuid.uuid4())
|
|
961
1166
|
|
|
962
1167
|
# 初始化任务状态
|
|
963
|
-
self.
|
|
964
|
-
"status": "pending",
|
|
965
|
-
"result": None,
|
|
966
|
-
"error": None,
|
|
967
|
-
}
|
|
1168
|
+
self._init_task(task_id, status="pending")
|
|
968
1169
|
|
|
969
1170
|
# 启动后台任务
|
|
970
1171
|
asyncio.create_task(
|
|
@@ -1017,11 +1218,7 @@ class KnowledgeBaseRoute(Route):
|
|
|
1017
1218
|
"""后台上传URL任务"""
|
|
1018
1219
|
try:
|
|
1019
1220
|
# 初始化任务状态
|
|
1020
|
-
self.
|
|
1021
|
-
"status": "processing",
|
|
1022
|
-
"result": None,
|
|
1023
|
-
"error": None,
|
|
1024
|
-
}
|
|
1221
|
+
self._init_task(task_id, status="processing")
|
|
1025
1222
|
self.upload_progress[task_id] = {
|
|
1026
1223
|
"status": "processing",
|
|
1027
1224
|
"file_index": 0,
|
|
@@ -1033,18 +1230,7 @@ class KnowledgeBaseRoute(Route):
|
|
|
1033
1230
|
}
|
|
1034
1231
|
|
|
1035
1232
|
# 创建进度回调函数
|
|
1036
|
-
|
|
1037
|
-
if task_id in self.upload_progress:
|
|
1038
|
-
self.upload_progress[task_id].update(
|
|
1039
|
-
{
|
|
1040
|
-
"status": "processing",
|
|
1041
|
-
"file_index": 0,
|
|
1042
|
-
"file_name": f"URL: {url}",
|
|
1043
|
-
"stage": stage,
|
|
1044
|
-
"current": current,
|
|
1045
|
-
"total": total,
|
|
1046
|
-
},
|
|
1047
|
-
)
|
|
1233
|
+
progress_callback = self._make_progress_callback(task_id, 0, f"URL: {url}")
|
|
1048
1234
|
|
|
1049
1235
|
# 上传文档
|
|
1050
1236
|
doc = await kb_helper.upload_from_url(
|
|
@@ -1069,20 +1255,9 @@ class KnowledgeBaseRoute(Route):
|
|
|
1069
1255
|
"failed_count": 0,
|
|
1070
1256
|
}
|
|
1071
1257
|
|
|
1072
|
-
self.
|
|
1073
|
-
"status": "completed",
|
|
1074
|
-
"result": result,
|
|
1075
|
-
"error": None,
|
|
1076
|
-
}
|
|
1077
|
-
self.upload_progress[task_id]["status"] = "completed"
|
|
1258
|
+
self._set_task_result(task_id, "completed", result=result)
|
|
1078
1259
|
|
|
1079
1260
|
except Exception as e:
|
|
1080
1261
|
logger.error(f"后台上传URL任务 {task_id} 失败: {e}")
|
|
1081
1262
|
logger.error(traceback.format_exc())
|
|
1082
|
-
self.
|
|
1083
|
-
"status": "failed",
|
|
1084
|
-
"result": None,
|
|
1085
|
-
"error": str(e),
|
|
1086
|
-
}
|
|
1087
|
-
if task_id in self.upload_progress:
|
|
1088
|
-
self.upload_progress[task_id]["status"] = "failed"
|
|
1263
|
+
self._set_task_result(task_id, "failed", error=str(e))
|
astrbot/dashboard/routes/log.py
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
import json
|
|
3
|
+
from typing import cast
|
|
3
4
|
|
|
5
|
+
from quart import Response as QuartResponse
|
|
4
6
|
from quart import make_response
|
|
5
7
|
|
|
6
8
|
from astrbot.core import LogBroker, logger
|
|
@@ -39,14 +41,17 @@ class LogRoute(Route):
|
|
|
39
41
|
if queue:
|
|
40
42
|
self.log_broker.unregister(queue)
|
|
41
43
|
|
|
42
|
-
response =
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
44
|
+
response = cast(
|
|
45
|
+
QuartResponse,
|
|
46
|
+
await make_response(
|
|
47
|
+
stream(),
|
|
48
|
+
{
|
|
49
|
+
"Content-Type": "text/event-stream",
|
|
50
|
+
"Cache-Control": "no-cache",
|
|
51
|
+
"Connection": "keep-alive",
|
|
52
|
+
"Transfer-Encoding": "chunked",
|
|
53
|
+
},
|
|
54
|
+
),
|
|
50
55
|
)
|
|
51
56
|
response.timeout = None
|
|
52
57
|
return response
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
"""统一 Webhook 路由
|
|
2
|
+
|
|
3
|
+
提供统一的 webhook 回调入口,支持多个平台使用同一端口接收回调。
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
from quart import request
|
|
7
|
+
|
|
8
|
+
from astrbot.core import logger
|
|
9
|
+
from astrbot.core.core_lifecycle import AstrBotCoreLifecycle
|
|
10
|
+
from astrbot.core.platform import Platform
|
|
11
|
+
|
|
12
|
+
from .route import Response, Route, RouteContext
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class PlatformRoute(Route):
|
|
16
|
+
"""统一 Webhook 路由"""
|
|
17
|
+
|
|
18
|
+
def __init__(
|
|
19
|
+
self,
|
|
20
|
+
context: RouteContext,
|
|
21
|
+
core_lifecycle: AstrBotCoreLifecycle,
|
|
22
|
+
) -> None:
|
|
23
|
+
super().__init__(context)
|
|
24
|
+
self.core_lifecycle = core_lifecycle
|
|
25
|
+
self.platform_manager = core_lifecycle.platform_manager
|
|
26
|
+
|
|
27
|
+
self._register_webhook_routes()
|
|
28
|
+
|
|
29
|
+
def _register_webhook_routes(self):
|
|
30
|
+
"""注册 webhook 路由"""
|
|
31
|
+
# 统一 webhook 入口,支持 GET 和 POST
|
|
32
|
+
self.app.add_url_rule(
|
|
33
|
+
"/api/platform/webhook/<webhook_uuid>",
|
|
34
|
+
view_func=self.unified_webhook_callback,
|
|
35
|
+
methods=["GET", "POST"],
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
# 平台统计信息接口
|
|
39
|
+
self.app.add_url_rule(
|
|
40
|
+
"/api/platform/stats",
|
|
41
|
+
view_func=self.get_platform_stats,
|
|
42
|
+
methods=["GET"],
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
async def unified_webhook_callback(self, webhook_uuid: str):
|
|
46
|
+
"""统一 webhook 回调入口
|
|
47
|
+
|
|
48
|
+
Args:
|
|
49
|
+
webhook_uuid: 平台配置中的 webhook_uuid
|
|
50
|
+
|
|
51
|
+
Returns:
|
|
52
|
+
根据平台适配器返回相应的响应
|
|
53
|
+
"""
|
|
54
|
+
# 根据 webhook_uuid 查找对应的平台
|
|
55
|
+
platform_adapter = self._find_platform_by_uuid(webhook_uuid)
|
|
56
|
+
|
|
57
|
+
if not platform_adapter:
|
|
58
|
+
logger.warning(f"未找到 webhook_uuid 为 {webhook_uuid} 的平台")
|
|
59
|
+
return Response().error("未找到对应平台").__dict__, 404
|
|
60
|
+
|
|
61
|
+
# 调用平台适配器的 webhook_callback 方法
|
|
62
|
+
try:
|
|
63
|
+
result = await platform_adapter.webhook_callback(request)
|
|
64
|
+
return result
|
|
65
|
+
except NotImplementedError:
|
|
66
|
+
logger.error(
|
|
67
|
+
f"平台 {platform_adapter.meta().name} 未实现 webhook_callback 方法"
|
|
68
|
+
)
|
|
69
|
+
return Response().error("平台未支持统一 Webhook 模式").__dict__, 500
|
|
70
|
+
except Exception as e:
|
|
71
|
+
logger.error(f"处理 webhook 回调时发生错误: {e}", exc_info=True)
|
|
72
|
+
return Response().error("处理回调失败").__dict__, 500
|
|
73
|
+
|
|
74
|
+
def _find_platform_by_uuid(self, webhook_uuid: str) -> Platform | None:
|
|
75
|
+
"""根据 webhook_uuid 查找对应的平台适配器
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
webhook_uuid: webhook UUID
|
|
79
|
+
|
|
80
|
+
Returns:
|
|
81
|
+
平台适配器实例,未找到则返回 None
|
|
82
|
+
"""
|
|
83
|
+
for platform in self.platform_manager.platform_insts:
|
|
84
|
+
if platform.config.get("webhook_uuid") == webhook_uuid:
|
|
85
|
+
if platform.unified_webhook():
|
|
86
|
+
return platform
|
|
87
|
+
return None
|
|
88
|
+
|
|
89
|
+
async def get_platform_stats(self):
|
|
90
|
+
"""获取所有平台的统计信息
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
包含平台统计信息的响应
|
|
94
|
+
"""
|
|
95
|
+
try:
|
|
96
|
+
stats = self.platform_manager.get_all_stats()
|
|
97
|
+
return Response().ok(stats).__dict__
|
|
98
|
+
except Exception as e:
|
|
99
|
+
logger.error(f"获取平台统计信息失败: {e}", exc_info=True)
|
|
100
|
+
return Response().error(f"获取统计信息失败: {e}").__dict__, 500
|