From 8ba42364025aa8d9842626b17c3bf056efee6e98 Mon Sep 17 00:00:00 2001 From: Ruochen <1051989940@qq.com> Date: Tue, 1 Jul 2025 15:41:30 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=E5=B0=86=E5=89=8D=E7=AB=AF=E6=B5=8B?= =?UTF-8?q?=E8=AF=95=E4=BE=9B=E5=BA=94=E5=95=86=E9=83=A8=E5=88=86=E4=BF=AE?= =?UTF-8?q?=E6=94=B9=E4=B8=BA=E7=8B=AC=E7=AB=8B=E5=B9=B6=E5=8F=91=E5=BC=82?= =?UTF-8?q?=E6=AD=A5=E8=8E=B7=E5=8F=96=E5=90=84=E4=B8=AA=E6=96=87=E6=9C=AC?= =?UTF-8?q?=E4=BE=9B=E5=BA=94=E5=95=86=E7=9A=84=E7=8A=B6=E6=80=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- astrbot/dashboard/routes/config.py | 43 +++---- .../i18n/locales/en-US/features/provider.json | 1 + .../i18n/locales/zh-CN/features/provider.json | 1 + dashboard/src/views/ProviderPage.vue | 106 +++++++++++++++--- 4 files changed, 114 insertions(+), 37 deletions(-) diff --git a/astrbot/dashboard/routes/config.py b/astrbot/dashboard/routes/config.py index c225c762a..866509605 100644 --- a/astrbot/dashboard/routes/config.py +++ b/astrbot/dashboard/routes/config.py @@ -166,7 +166,7 @@ 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), + "/config/provider/check_one": ("GET", self.check_one_provider_status), "/config/provider/list": ("GET", self.get_provider_config_list), "/config/provider/get_session_seperate": ( "GET", @@ -256,34 +256,39 @@ class ConfigRoute(Route): ) return status_info - async def check_all_providers_status(self): + async def check_one_provider_status(self): """ - API 接口: 检查所有 LLM Providers 的状态 + API 接口: 检查单个 LLM Provider 的状态 """ - logger.info("API call received: /config/provider/check_status") + provider_id = request.args.get("id") + if not provider_id: + return Response().error("Missing provider_id parameter", status_code=400).__dict__ + + logger.info(f"API call received: /config/provider/check_one for id: {provider_id}") + try: all_providers: typing.List = ( self.core_lifecycle.star_context.get_all_providers() ) - logger.debug(f"Found {len(all_providers)} providers to check.") + + target_provider = None + for p in all_providers: + # provider.provider_config 是 AstrBotConfig 对象,可以直接当字典用 + if p.provider_config.get("id") == provider_id: + target_provider = p + break + + if not target_provider: + logger.warning(f"Provider with id '{provider_id}' not found for status check.") + return Response().error(f"Provider with id '{provider_id}' not found", status_code=404).__dict__ - if not all_providers: - logger.info("No providers found to check.") - return Response().ok([]).__dict__ + result = await self._test_single_provider(target_provider) + return Response().ok(result).__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(f"Critical error in check_one_provider_status for id {provider_id}: {str(e)}") logger.error(traceback.format_exc()) - return ( - Response().error(f"检查 Provider 状态时发生严重错误: {str(e)}").__dict__ - ) + return Response().error(f"检查 Provider 状态时发生严重错误: {str(e)}", status_code=500).__dict__ async def get_configs(self): # plugin_name 为空时返回 AstrBot 配置 diff --git a/dashboard/src/i18n/locales/en-US/features/provider.json b/dashboard/src/i18n/locales/en-US/features/provider.json index c64f23d19..5ecf890ad 100644 --- a/dashboard/src/i18n/locales/en-US/features/provider.json +++ b/dashboard/src/i18n/locales/en-US/features/provider.json @@ -29,6 +29,7 @@ "noData": "Click \"Refresh Status\" button to get service provider availability", "available": "Available", "unavailable": "Unavailable", + "pending": "Pending...", "errorMessage": "Error Message" }, "logs": { diff --git a/dashboard/src/i18n/locales/zh-CN/features/provider.json b/dashboard/src/i18n/locales/zh-CN/features/provider.json index e9bb63c6d..fdf07aefa 100644 --- a/dashboard/src/i18n/locales/zh-CN/features/provider.json +++ b/dashboard/src/i18n/locales/zh-CN/features/provider.json @@ -29,6 +29,7 @@ "noData": "点击\"刷新状态\"按钮获取服务提供商可用性", "available": "可用", "unavailable": "不可用", + "pending": "检查中...", "errorMessage": "错误信息" }, "logs": { diff --git a/dashboard/src/views/ProviderPage.vue b/dashboard/src/views/ProviderPage.vue index 44e7f8206..027c39b55 100644 --- a/dashboard/src/views/ProviderPage.vue +++ b/dashboard/src/views/ProviderPage.vue @@ -115,14 +115,23 @@ - + - - {{ status.status === 'available' ? 'mdi-check-circle' : 'mdi-alert-circle' }} - + mdi-check-circle + mdi-alert-circle + + {{ status.id }} - - {{ status.status === 'available' ? tm('availability.available') : tm('availability.unavailable') }} + + + {{ getStatusText(status.status) }} @@ -470,10 +479,16 @@ export default { sessionSeparation: this.tm('messages.success.sessionSeparation') }, error: { - sessionSeparation: this.tm('messages.error.sessionSeparation') + sessionSeparation: this.tm('messages.error.sessionSeparation'), + fetchStatus: this.tm('messages.error.fetchStatus') }, confirm: { delete: this.tm('messages.confirm.delete') + }, + status: { + available: this.tm('availability.available'), + unavailable: this.tm('availability.unavailable'), + pending: this.tm('availability.pending') } }; }, @@ -763,19 +778,58 @@ export default { }, // 获取供应商状态 - fetchProviderStatus() { + async fetchProviderStatus() { + if (this.loadingStatus) return; + 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.tm('messages.error.fetchStatus')); - } - this.loadingStatus = false; - }).catch((err) => { - this.loadingStatus = false; - this.showError(err.response?.data?.message || err.message); + + // 1. 立即初始化UI为pending状态 + this.providerStatuses = this.config_data.provider.map(p => ({ + id: p.id, + name: p.id, + status: 'pending', + error: null + })); + + // 2. 为每个provider创建一个并发的测试请求 + const promises = this.config_data.provider.map(p => { + return axios.get(`/api/config/provider/check_one?id=${p.id}`) + .then(res => { + if (res.data && res.data.status === 'ok') { + // 成功,更新对应的provider状态 + const index = this.providerStatuses.findIndex(s => s.id === p.id); + if (index !== -1) { + this.providerStatuses.splice(index, 1, res.data.data); + } + } else { + // 接口返回了业务错误 + throw new Error(res.data?.message || `Failed to check status for ${p.id}`); + } + }) + .catch(err => { + // 网络错误或业务错误 + const errorMessage = err.response?.data?.message || err.message || 'Unknown error'; + const index = this.providerStatuses.findIndex(s => s.id === p.id); + if (index !== -1) { + const failedStatus = { + ...this.providerStatuses[index], + status: 'unavailable', + error: errorMessage + }; + this.providerStatuses.splice(index, 1, failedStatus); + } + // 可以在这里选择性地向上抛出错误,以便Promise.allSettled知道 + return Promise.reject(errorMessage); + }); }); + + // 3. 等待所有请求完成(无论成功或失败) + try { + await Promise.allSettled(promises); + } finally { + // 4. 关闭全局加载状态 + this.loadingStatus = false; + } }, confirmEmptyKey() { @@ -806,6 +860,22 @@ export default { } this.showIdConflictDialog = false; }, + getStatusColor(status) { + switch (status) { + case 'available': + return 'success'; + case 'unavailable': + return 'error'; + case 'pending': + return 'grey'; + default: + return 'default'; + } + }, + + getStatusText(status) { + return this.messages.status[status] || status; + }, } }