From b27271b7a313cd392b1f2d1e0aef817eb35d886b Mon Sep 17 00:00:00 2001
From: Ruochen <1051989940@qq.com>
Date: Fri, 30 May 2025 15:10:15 +0800
Subject: [PATCH] =?UTF-8?q?feat:=E6=B7=BB=E5=8A=A0=E6=B5=8B=E8=AF=95?=
=?UTF-8?q?=E6=96=87=E6=9C=AC=E7=94=9F=E6=88=90=E4=BE=9B=E5=BA=94=E5=95=86?=
=?UTF-8?q?=E5=8F=AF=E7=94=A8=E5=8A=9F=E8=83=BD?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
astrbot/dashboard/routes/config.py | 75 ++++++++++++++++++++++++++++
dashboard/src/views/ProviderPage.vue | 65 ++++++++++++++++++++++++
2 files changed, 140 insertions(+)
diff --git a/astrbot/dashboard/routes/config.py b/astrbot/dashboard/routes/config.py
index b2677de10..001f5722d 100644
--- a/astrbot/dashboard/routes/config.py
+++ b/astrbot/dashboard/routes/config.py
@@ -9,6 +9,7 @@ from astrbot.core.platform.register import platform_registry
from astrbot.core.provider.register import provider_registry
from astrbot.core.star.star import star_registry
from astrbot.core import logger
+import asyncio # 用于并发执行获取供应商请求
def try_cast(value: str, type_: str):
@@ -164,9 +165,83 @@ class ConfigRoute(Route):
"/config/provider/update": ("POST", self.post_update_provider),
"/config/provider/delete": ("POST", self.post_delete_provider),
"/config/llmtools": ("GET", self.get_llm_tools),
+ "/config/provider/check_status": ("GET", self.check_all_providers_status),
}
self.register_routes()
+ async def _test_single_provider(self, provider):
+ """辅助函数:测试单个 provider 的可用性"""
+ meta = provider.meta()
+ provider_name = provider.provider_config.get("name", meta.id if meta else meta.id)
+ if not provider_name and meta:
+ provider_name = meta.id
+ elif not provider_name:
+ provider_name = "Unknown Provider"
+ status_info = {
+ "id": meta.id if meta else "Unknown ID",
+ "model": meta.model if meta else "Unknown Model",
+ "type": meta.type if meta else "Unknown Type",
+ "name": provider_name,
+ "status": "unavailable", # 默认为不可用
+ "error": None,
+ }
+ logger.debug(f"Attempting to check provider: {status_info['name']} (ID: {status_info['id']}, Type: {status_info['type']}, Model: {status_info['model']})")
+ try:
+ logger.debug(f"Sending 'Ping' to provider: {status_info['name']}")
+ response = await asyncio.wait_for(provider.text_chat(prompt="Ping"), timeout=20.0) #超时二十秒
+ logger.debug(f"Received response from {status_info['name']}: {response}")
+ # 只要 text_chat 调用成功返回一个 LLMResponse 对象 (即 response 不为 None),就认为可用
+ if response is not None:
+ status_info["status"] = "available"
+ response_text_snippet = ""
+ if hasattr(response, 'completion_text') and response.completion_text:
+ response_text_snippet = response.completion_text[:70] + "..." if len(response.completion_text) > 70 else response.completion_text
+ elif hasattr(response, 'result_chain') and response.result_chain:
+ try:
+ response_text_snippet = response.result_chain.get_plain_text()[:70] + "..." if len(response.result_chain.get_plain_text()) > 70 else response.result_chain.get_plain_text()
+ except:
+ pass
+ logger.info(f"Provider {status_info['name']} (ID: {status_info['id']}) is available. Response snippet: '{response_text_snippet}'")
+ else:
+ # 这个分支理论上不应该被走到,除非 text_chat 实现可能返回 None
+ status_info["error"] = "Test call returned None, but expected an LLMResponse object."
+ logger.warning(f"Provider {status_info['name']} (ID: {status_info['id']}) test call returned None.")
+
+ except asyncio.TimeoutError:
+ status_info["error"] = "Connection timed out after 10 seconds during test call."
+ logger.warning(f"Provider {status_info['name']} (ID: {status_info['id']}) timed out.")
+ except Exception as e:
+ error_message = str(e)
+ status_info["error"] = error_message
+ logger.warning(f"Provider {status_info['name']} (ID: {status_info['id']}) is unavailable. Error: {error_message}")
+ logger.debug(f"Traceback for {status_info['name']}:\n{traceback.format_exc()}")
+ return status_info
+
+ async def check_all_providers_status(self):
+ """
+ API 接口: 检查所有 LLM Providers 的状态
+ """
+ logger.info("API call received: /config/provider/check_status")
+ try:
+ all_providers: typing.List = self.core_lifecycle.star_context.get_all_providers()
+ logger.debug(f"Found {len(all_providers)} providers to check.")
+
+ if not all_providers:
+ logger.info("No providers found to check.")
+ return Response().ok([]).__dict__
+
+ tasks = [self._test_single_provider(p) for p in all_providers]
+ logger.debug(f"Created {len(tasks)} tasks for concurrent provider checks.")
+
+ results = await asyncio.gather(*tasks)
+ logger.info(f"Provider status check completed. Results: {results}")
+
+ return Response().ok(results).__dict__
+ except Exception as e:
+ logger.error(f"Critical error in check_all_providers_status: {str(e)}")
+ logger.error(traceback.format_exc())
+ return Response().error(f"检查 Provider 状态时发生严重错误: {str(e)}").__dict__
+
async def get_configs(self):
# plugin_name 为空时返回 AstrBot 配置
# 否则返回指定 plugin_name 的插件配置
diff --git a/dashboard/src/views/ProviderPage.vue b/dashboard/src/views/ProviderPage.vue
index 8bad17e4d..ea0ace92a 100644
--- a/dashboard/src/views/ProviderPage.vue
+++ b/dashboard/src/views/ProviderPage.vue
@@ -61,6 +61,51 @@
+
+
+
+ mdi-heart-pulse
+ 供应商可用性
+
+
+ mdi-refresh
+ 刷新状态
+
+
+
+ 通过测试模型对话可用性判断,可能产生API费用
+
+
+
+
+
+
+ 点击"刷新状态"按钮获取供应商可用性
+
+
+
+
+
+
+
+
+ {{ status.status === 'available' ? 'mdi-check-circle' : 'mdi-alert-circle' }}
+
+ {{ status.id }}
+
+ {{ status.status === 'available' ? '可用' : '不可用' }}
+
+
+
+ 错误信息: {{ status.error }}
+
+
+
+
+
+
+
+
@@ -221,6 +266,10 @@ export default {
save_message_success: "success",
showConsole: false,
+
+ // 供应商状态相关
+ providerStatuses: [],
+ loadingStatus: false,
// 新增提供商对话框相关
showAddProviderDialog: false,
@@ -442,6 +491,22 @@ export default {
this.save_message = message;
this.save_message_success = "error";
this.save_message_snack = true;
+ },
+
+ // 获取供应商状态
+ fetchProviderStatus() {
+ this.loadingStatus = true;
+ axios.get('/api/config/provider/check_status').then((res) => {
+ if (res.data && res.data.status === 'ok') {
+ this.providerStatuses = res.data.data || [];
+ } else {
+ this.showError(res.data?.message || "获取供应商状态失败");
+ }
+ this.loadingStatus = false;
+ }).catch((err) => {
+ this.loadingStatus = false;
+ this.showError(err.response?.data?.message || err.message);
+ });
}
}
}