feat: 全栈功能增强 - 扩展 provider/pool 管理、完善调度与数据层、重构前端 Pool 页面

后端:
- 扩展 pool_admin payloads 和 provider query models,增强 endpoint key 管理
- 完善 scheduler-core 候选排序与请求候选逻辑
- 增强 usage-runtime 写入、provider-transport 网络层与 OAuth 刷新
- 改进 AI pipeline 响应转换与流式处理
- 扩展 global_models/provider_catalog 数据层查询能力
- 增强 video-tasks-core 多 provider 支持
- 新增大量集成测试覆盖 pool/keys/provider_query/frontdoor

前端:
- 重构 PoolManagement 页面,拆分状态管理/对话框逻辑到独立模块
- 新增 poolAdvancedDialog/poolSchedulingDialog/poolManagementState/poolMobilePresentation 工具函数及测试
- 改进 Dialog 组件与 provider tabs 显示

部署:
- 更新 Rust CI workflow 和 Dockerfile 构建配置

Closes #275
Co-authored-by: AAEE86 <ppk0227@hotmail.com>
This commit is contained in:
fawney19
2026-04-09 13:51:50 +08:00
parent 4fc95adfb9
commit b0b40c16ff
97 changed files with 5816 additions and 1881 deletions

View File

@@ -41,6 +41,31 @@ pub struct SchedulerExecutionRequestCandidateSeed {
pub report_context: Value,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SchedulerRequestCandidateStatusUpdate {
pub status: RequestCandidateStatus,
pub status_code: Option<u16>,
pub error_type: Option<String>,
pub error_message: Option<String>,
pub latency_ms: Option<u64>,
pub started_at_unix_secs: Option<u64>,
pub finished_at_unix_secs: Option<u64>,
}
#[derive(Debug, Clone)]
pub struct LocalRequestCandidateStatusRecordInput<'a> {
pub plan: &'a ExecutionPlan,
pub report_context: Option<&'a Value>,
pub status_update: SchedulerRequestCandidateStatusUpdate,
}
#[derive(Debug, Clone)]
pub struct ReportRequestCandidateStatusRecordInput {
pub slot: SchedulerResolvedReportRequestCandidateSlot,
pub status_update: SchedulerRequestCandidateStatusUpdate,
pub now_unix_secs: u64,
}
pub fn execution_error_details(
error: Option<&ExecutionError>,
body_json: Option<&Value>,
@@ -231,16 +256,23 @@ pub fn build_execution_request_candidate_seed(
}
pub fn build_local_request_candidate_status_record(
plan: &ExecutionPlan,
report_context: Option<&Value>,
status: RequestCandidateStatus,
status_code: Option<u16>,
error_type: Option<String>,
error_message: Option<String>,
latency_ms: Option<u64>,
started_at_unix_secs: Option<u64>,
finished_at_unix_secs: Option<u64>,
input: LocalRequestCandidateStatusRecordInput<'_>,
) -> Option<UpsertRequestCandidateRecord> {
let LocalRequestCandidateStatusRecordInput {
plan,
report_context,
status_update,
} = input;
let SchedulerRequestCandidateStatusUpdate {
status,
status_code,
error_type,
error_message,
latency_ms,
started_at_unix_secs,
finished_at_unix_secs,
} = status_update;
let candidate_id = plan
.candidate_id
.as_deref()
@@ -278,16 +310,23 @@ pub fn build_local_request_candidate_status_record(
}
pub fn build_report_request_candidate_status_record(
slot: SchedulerResolvedReportRequestCandidateSlot,
status: RequestCandidateStatus,
status_code: Option<u16>,
error_type: Option<String>,
error_message: Option<String>,
latency_ms: Option<u64>,
started_at_unix_secs: Option<u64>,
finished_at_unix_secs: Option<u64>,
now_unix_secs: u64,
input: ReportRequestCandidateStatusRecordInput,
) -> UpsertRequestCandidateRecord {
let ReportRequestCandidateStatusRecordInput {
slot,
status_update,
now_unix_secs,
} = input;
let SchedulerRequestCandidateStatusUpdate {
status,
status_code,
error_type,
error_message,
latency_ms,
started_at_unix_secs,
finished_at_unix_secs,
} = status_update;
let terminal_unix_secs = finished_at_unix_secs.unwrap_or(now_unix_secs);
let started_at_unix_secs = started_at_unix_secs
.or(slot.started_at_unix_secs)
@@ -454,7 +493,8 @@ mod tests {
build_report_request_candidate_status_record, execution_error_details,
finalize_execution_request_candidate_report_context,
parse_request_candidate_report_context, resolve_report_request_candidate_slot,
SchedulerResolvedReportRequestCandidateSlot,
LocalRequestCandidateStatusRecordInput, ReportRequestCandidateStatusRecordInput,
SchedulerRequestCandidateStatusUpdate, SchedulerResolvedReportRequestCandidateSlot,
};
fn sample_candidate(
@@ -605,23 +645,26 @@ mod tests {
let mut plan = sample_plan();
plan.candidate_id = Some("cand-1".to_string());
let record = build_local_request_candidate_status_record(
&plan,
Some(&json!({
"candidate_index": 1,
"retry_index": 2,
"user_id": "user-1",
"api_key_id": "api-key-1"
})),
RequestCandidateStatus::Failed,
Some(500),
Some("Upstream5xx".to_string()),
Some("boom".to_string()),
Some(42),
Some(100),
Some(101),
)
.expect("record should build");
let record =
build_local_request_candidate_status_record(LocalRequestCandidateStatusRecordInput {
plan: &plan,
report_context: Some(&json!({
"candidate_index": 1,
"retry_index": 2,
"user_id": "user-1",
"api_key_id": "api-key-1"
})),
status_update: SchedulerRequestCandidateStatusUpdate {
status: RequestCandidateStatus::Failed,
status_code: Some(500),
error_type: Some("Upstream5xx".to_string()),
error_message: Some("boom".to_string()),
latency_ms: Some(42),
started_at_unix_secs: Some(100),
finished_at_unix_secs: Some(101),
},
})
.expect("record should build");
assert_eq!(record.id, "cand-1");
assert_eq!(record.candidate_index, 1);
@@ -632,31 +675,34 @@ mod tests {
#[test]
fn builds_report_request_candidate_status_record_with_terminal_timestamps() {
let record = build_report_request_candidate_status_record(
SchedulerResolvedReportRequestCandidateSlot {
id: "cand-1".to_string(),
request_id: "req-1".to_string(),
user_id: Some("user-1".to_string()),
api_key_id: Some("api-key-1".to_string()),
candidate_index: 1,
retry_index: 0,
provider_id: Some("provider-1".to_string()),
endpoint_id: Some("endpoint-1".to_string()),
key_id: Some("key-1".to_string()),
extra_data: None,
created_at_unix_secs: 10,
started_at_unix_secs: None,
finished_at_unix_secs: None,
},
RequestCandidateStatus::Success,
Some(200),
None,
None,
Some(12),
None,
None,
123,
);
let record =
build_report_request_candidate_status_record(ReportRequestCandidateStatusRecordInput {
slot: SchedulerResolvedReportRequestCandidateSlot {
id: "cand-1".to_string(),
request_id: "req-1".to_string(),
user_id: Some("user-1".to_string()),
api_key_id: Some("api-key-1".to_string()),
candidate_index: 1,
retry_index: 0,
provider_id: Some("provider-1".to_string()),
endpoint_id: Some("endpoint-1".to_string()),
key_id: Some("key-1".to_string()),
extra_data: None,
created_at_unix_secs: 10,
started_at_unix_secs: None,
finished_at_unix_secs: None,
},
status_update: SchedulerRequestCandidateStatusUpdate {
status: RequestCandidateStatus::Success,
status_code: Some(200),
error_type: None,
error_message: None,
latency_ms: Some(12),
started_at_unix_secs: None,
finished_at_unix_secs: None,
},
now_unix_secs: 123,
});
assert_eq!(record.started_at_unix_secs, Some(123));
assert_eq!(record.finished_at_unix_secs, Some(123));