diff --git a/astrbot/dashboard/routes/config.py b/astrbot/dashboard/routes/config.py index b55f0b21c..1dbe4de4a 100644 --- a/astrbot/dashboard/routes/config.py +++ b/astrbot/dashboard/routes/config.py @@ -167,7 +167,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/model_list": ("GET", self.get_provider_model_list), "/config/provider/get_session_seperate": ( @@ -258,33 +258,37 @@ class ConfigRoute(Route): ) return status_info - async def check_all_providers_status(self): - """ - API 接口: 检查所有 LLM Providers 的状态 - """ - logger.info("API call received: /config/provider/check_status") + def _error_response(self, message: str, status_code: int = 500, log_fn=logger.error): + log_fn(message) + # 记录更详细的traceback信息,但只在是严重错误时 + if status_code == 500: + log_fn(traceback.format_exc()) + return Response().error(message, status_code=status_code).__dict__ + + async def check_one_provider_status(self): + """API: check a single LLM Provider's status by id""" + provider_id = request.args.get("id") + if not provider_id: + return self._error_response("Missing provider_id parameter", 400, logger.warning) + + logger.info(f"API call: /config/provider/check_one id={provider_id}") try: - all_providers: typing.List = ( - self.core_lifecycle.star_context.get_all_providers() + all_providers = self.core_lifecycle.star_context.get_all_providers() + # replace manual loop with next(filter(...)) + target = next( + (p for p in all_providers if p.provider_config.get("id") == provider_id), + None ) - logger.debug(f"Found {len(all_providers)} providers to check.") + if not target: + return self._error_response(f"Provider with id '{provider_id}' not found", 404, logger.warning) - if not all_providers: - logger.info("No providers found to check.") - return Response().ok([]).__dict__ + result = await self._test_single_provider(target) + 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(traceback.format_exc()) - return ( - Response().error(f"检查 Provider 状态时发生严重错误: {str(e)}").__dict__ + return self._error_response( + f"Critical error checking provider {provider_id}: {e}", + 500 ) async def get_configs(self): 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; + }, } }