Merge pull request #1984 from RC-CHN/master

refactor:将前端测试供应商部分修改为独立并发异步获取各个文本供应商的状态
This commit is contained in:
Soulter
2025-07-03 10:55:51 +08:00
committed by GitHub
4 changed files with 117 additions and 41 deletions
+27 -23
View File
@@ -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):
@@ -29,6 +29,7 @@
"noData": "Click \"Refresh Status\" button to get service provider availability",
"available": "Available",
"unavailable": "Unavailable",
"pending": "Pending...",
"errorMessage": "Error Message"
},
"logs": {
@@ -29,6 +29,7 @@
"noData": "点击\"刷新状态\"按钮获取服务提供商可用性",
"available": "可用",
"unavailable": "不可用",
"pending": "检查中...",
"errorMessage": "错误信息"
},
"logs": {
+88 -18
View File
@@ -115,14 +115,23 @@
<v-container v-else class="pa-0">
<v-row>
<v-col v-for="status in providerStatuses" :key="status.id" cols="12" sm="6" md="4">
<v-card variant="outlined" class="status-card">
<v-card variant="outlined" class="status-card" :class="`status-${status.status}`">
<v-card-item>
<v-icon :color="status.status === 'available' ? 'success' : 'error'" class="me-2">
{{ status.status === 'available' ? 'mdi-check-circle' : 'mdi-alert-circle' }}
</v-icon>
<v-icon v-if="status.status === 'available'" color="success" class="me-2">mdi-check-circle</v-icon>
<v-icon v-else-if="status.status === 'unavailable'" color="error" class="me-2">mdi-alert-circle</v-icon>
<v-progress-circular
v-else-if="status.status === 'pending'"
indeterminate
color="primary"
size="20"
width="2"
class="me-2"
></v-progress-circular>
<span class="font-weight-bold">{{ status.id }}</span>
<v-chip :color="status.status === 'available' ? 'success' : 'error'" size="small" class="ml-2">
{{ status.status === 'available' ? tm('availability.available') : tm('availability.unavailable') }}
<v-chip :color="getStatusColor(status.status)" size="small" class="ml-2">
{{ getStatusText(status.status) }}
</v-chip>
</v-card-item>
<v-card-text v-if="status.status === 'unavailable'" class="text-caption text-medium-emphasis">
@@ -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;
},
}
}
</script>