mirror of
https://github.com/fawney19/Aether.git
synced 2026-10-11 11:49:50 +08:00
refactor: 大规模模块拆分与代码精简,新增 ai-pipeline/data-contracts 独立 crate
- 新增 aether-ai-pipeline 和 aether-data-contracts crate,将 pipeline 逻辑与数据契约从 gateway 中解耦 - 重构 admin handlers:拆分单体模块为 auth/billing/endpoint/features/model/observability/provider/system 等独立子模块 - 合并 chat/cli 重复代码路径:精简 conversion、finalize、planner 中的 sync/chat/cli 分支 - 重构 scheduler/executor/data 层,引入 facade 模式降低模块间耦合 - 移除冗余的 intent 模块,将 plan_fallback/policy/stream_path/sync_path 迁移至 executor - 前端适配:调整 admin API 调用和 provider 模型测试对话框
This commit is contained in:
@@ -0,0 +1,351 @@
|
||||
use crate::async_task;
|
||||
use crate::control::GatewayPublicRequestContext;
|
||||
use crate::handlers::admin::shared::{attach_admin_audit_response, query_param_value};
|
||||
use crate::{AppState, GatewayError};
|
||||
use aether_data_contracts::repository::video_tasks::{VideoTaskQueryFilter, VideoTaskStatus};
|
||||
use axum::{
|
||||
body::Body,
|
||||
http,
|
||||
response::{IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use serde_json::json;
|
||||
|
||||
use super::super::super::auth::build_proxy_error_response;
|
||||
use super::builders::{
|
||||
admin_video_task_detail_id_from_path, admin_video_task_nested_id_from_path,
|
||||
admin_video_task_status_name, admin_video_task_timestamp, build_admin_video_task_list_item,
|
||||
build_admin_video_task_provider_names, current_admin_video_task_unix_secs,
|
||||
};
|
||||
|
||||
pub(super) async fn maybe_build_local_admin_video_tasks_response(
|
||||
state: &AppState,
|
||||
request_context: &GatewayPublicRequestContext,
|
||||
) -> Result<Option<Response<Body>>, GatewayError> {
|
||||
let Some(decision) = request_context.control_decision.as_ref() else {
|
||||
return Ok(None);
|
||||
};
|
||||
if decision.route_family.as_deref() != Some("video_tasks_manage") {
|
||||
return Ok(None);
|
||||
}
|
||||
|
||||
if request_context.request_method == http::Method::GET
|
||||
&& matches!(
|
||||
request_context.request_path.as_str(),
|
||||
"/api/admin/video-tasks" | "/api/admin/video-tasks/"
|
||||
)
|
||||
{
|
||||
let status =
|
||||
match query_param_value(request_context.request_query_string.as_deref(), "status") {
|
||||
Some(value) => match VideoTaskStatus::from_database(&value) {
|
||||
Ok(status) => Some(status),
|
||||
Err(err) => {
|
||||
return Ok(Some(build_proxy_error_response(
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
"invalid_request",
|
||||
err.to_string(),
|
||||
None,
|
||||
)));
|
||||
}
|
||||
},
|
||||
None => None,
|
||||
};
|
||||
let filter = VideoTaskQueryFilter {
|
||||
user_id: query_param_value(request_context.request_query_string.as_deref(), "user_id"),
|
||||
status,
|
||||
model_substring: query_param_value(
|
||||
request_context.request_query_string.as_deref(),
|
||||
"model",
|
||||
),
|
||||
client_api_format: None,
|
||||
};
|
||||
let page = query_param_value(request_context.request_query_string.as_deref(), "page")
|
||||
.and_then(|value| value.parse::<usize>().ok())
|
||||
.unwrap_or(1);
|
||||
let page_size =
|
||||
query_param_value(request_context.request_query_string.as_deref(), "page_size")
|
||||
.and_then(|value| value.parse::<usize>().ok())
|
||||
.unwrap_or(20);
|
||||
let response = async_task::read_video_task_page(state, &filter, page, page_size).await?;
|
||||
let provider_names = build_admin_video_task_provider_names(state, &response.items).await?;
|
||||
return Ok(Some(
|
||||
Json(json!({
|
||||
"items": response
|
||||
.items
|
||||
.iter()
|
||||
.map(|task| build_admin_video_task_list_item(task, &provider_names))
|
||||
.collect::<Vec<_>>(),
|
||||
"total": response.total,
|
||||
"page": response.page,
|
||||
"page_size": response.page_size,
|
||||
"pages": response.pages,
|
||||
}))
|
||||
.into_response(),
|
||||
));
|
||||
}
|
||||
|
||||
if request_context.request_method == http::Method::GET
|
||||
&& matches!(
|
||||
request_context.request_path.as_str(),
|
||||
"/api/admin/video-tasks/stats" | "/api/admin/video-tasks/stats/"
|
||||
)
|
||||
{
|
||||
let filter = VideoTaskQueryFilter {
|
||||
user_id: None,
|
||||
status: None,
|
||||
model_substring: None,
|
||||
client_api_format: None,
|
||||
};
|
||||
let stats =
|
||||
async_task::read_video_task_stats(state, &filter, current_admin_video_task_unix_secs())
|
||||
.await?;
|
||||
let active_users = state.count_distinct_video_task_users(&filter).await?;
|
||||
return Ok(Some(
|
||||
Json(json!({
|
||||
"total": stats.total,
|
||||
"by_status": stats.by_status,
|
||||
"by_model": stats.by_model,
|
||||
"today_count": stats.today_count,
|
||||
"active_users": active_users,
|
||||
"processing_count": stats.processing_count,
|
||||
}))
|
||||
.into_response(),
|
||||
));
|
||||
}
|
||||
|
||||
if request_context.request_method == http::Method::POST {
|
||||
let Some(task_id) =
|
||||
admin_video_task_nested_id_from_path(&request_context.request_path, "/cancel")
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let stored = match async_task::cancel_video_task_record(state, task_id).await {
|
||||
Ok(stored) => stored,
|
||||
Err(async_task::CancelVideoTaskError::NotFound) => {
|
||||
return Ok(Some(
|
||||
(
|
||||
http::StatusCode::NOT_FOUND,
|
||||
Json(json!({ "detail": "Video task not found" })),
|
||||
)
|
||||
.into_response(),
|
||||
));
|
||||
}
|
||||
Err(async_task::CancelVideoTaskError::InvalidStatus(status)) => {
|
||||
return Ok(Some(
|
||||
(
|
||||
http::StatusCode::BAD_REQUEST,
|
||||
Json(json!({
|
||||
"detail": format!(
|
||||
"Cannot cancel task with status: {}",
|
||||
admin_video_task_status_name(status),
|
||||
),
|
||||
})),
|
||||
)
|
||||
.into_response(),
|
||||
));
|
||||
}
|
||||
Err(async_task::CancelVideoTaskError::Response(response)) => {
|
||||
return Ok(Some(response));
|
||||
}
|
||||
Err(async_task::CancelVideoTaskError::Gateway(err)) => {
|
||||
return Err(err);
|
||||
}
|
||||
};
|
||||
return Ok(Some(attach_admin_audit_response(
|
||||
Json(json!({
|
||||
"id": stored.id,
|
||||
"status": "cancelled",
|
||||
"message": "Task cancelled successfully",
|
||||
}))
|
||||
.into_response(),
|
||||
"admin_video_task_cancelled",
|
||||
"cancel_video_task",
|
||||
"video_task",
|
||||
&stored.id,
|
||||
)));
|
||||
}
|
||||
|
||||
if request_context.request_method == http::Method::GET {
|
||||
let Some(task_id) =
|
||||
admin_video_task_nested_id_from_path(&request_context.request_path, "/video")
|
||||
else {
|
||||
let Some(task_id) = admin_video_task_detail_id_from_path(&request_context.request_path)
|
||||
else {
|
||||
return Ok(None);
|
||||
};
|
||||
let Some(task) = async_task::read_video_task_detail(state, task_id).await? else {
|
||||
return Ok(Some(
|
||||
(
|
||||
http::StatusCode::NOT_FOUND,
|
||||
Json(json!({ "detail": "Video task not found" })),
|
||||
)
|
||||
.into_response(),
|
||||
));
|
||||
};
|
||||
let provider_names =
|
||||
build_admin_video_task_provider_names(state, std::slice::from_ref(&task)).await?;
|
||||
let provider_name = task
|
||||
.provider_id
|
||||
.as_deref()
|
||||
.and_then(|provider_id| provider_names.get(provider_id))
|
||||
.cloned()
|
||||
.unwrap_or_else(|| "Unknown".to_string());
|
||||
let endpoint = if let Some(endpoint_id) = task.endpoint_id.as_ref() {
|
||||
let endpoints = state
|
||||
.read_provider_catalog_endpoints_by_ids(std::slice::from_ref(endpoint_id))
|
||||
.await?;
|
||||
endpoints.into_iter().next()
|
||||
} else {
|
||||
None
|
||||
};
|
||||
let endpoint_payload = endpoint.map(|endpoint| {
|
||||
json!({
|
||||
"id": endpoint.id,
|
||||
"base_url": endpoint.base_url,
|
||||
"api_format": endpoint.api_format,
|
||||
})
|
||||
});
|
||||
let mut payload = serde_json::Map::new();
|
||||
payload.insert("id".to_string(), json!(task.id));
|
||||
payload.insert("external_task_id".to_string(), json!(task.external_task_id));
|
||||
payload.insert("user_id".to_string(), json!(task.user_id));
|
||||
payload.insert(
|
||||
"username".to_string(),
|
||||
json!(task
|
||||
.username
|
||||
.clone()
|
||||
.unwrap_or_else(|| "Unknown".to_string())),
|
||||
);
|
||||
payload.insert("api_key_id".to_string(), json!(task.api_key_id));
|
||||
payload.insert("provider_id".to_string(), json!(task.provider_id));
|
||||
payload.insert("provider_name".to_string(), json!(provider_name));
|
||||
payload.insert("endpoint_id".to_string(), json!(task.endpoint_id));
|
||||
payload.insert(
|
||||
"endpoint".to_string(),
|
||||
endpoint_payload.unwrap_or(serde_json::Value::Null),
|
||||
);
|
||||
payload.insert("key_id".to_string(), json!(task.key_id));
|
||||
payload.insert(
|
||||
"client_api_format".to_string(),
|
||||
json!(task.client_api_format),
|
||||
);
|
||||
payload.insert(
|
||||
"provider_api_format".to_string(),
|
||||
json!(task.provider_api_format),
|
||||
);
|
||||
payload.insert("format_converted".to_string(), json!(task.format_converted));
|
||||
payload.insert("model".to_string(), json!(task.model));
|
||||
payload.insert("prompt".to_string(), json!(task.prompt));
|
||||
payload.insert(
|
||||
"original_request_body".to_string(),
|
||||
json!(task.original_request_body),
|
||||
);
|
||||
payload.insert(
|
||||
"converted_request_body".to_string(),
|
||||
serde_json::Value::Null,
|
||||
);
|
||||
payload.insert("duration_seconds".to_string(), json!(task.duration_seconds));
|
||||
payload.insert("resolution".to_string(), json!(task.resolution));
|
||||
payload.insert("aspect_ratio".to_string(), json!(task.aspect_ratio));
|
||||
payload.insert("size".to_string(), json!(task.size));
|
||||
payload.insert(
|
||||
"status".to_string(),
|
||||
json!(admin_video_task_status_name(task.status)),
|
||||
);
|
||||
payload.insert("progress_percent".to_string(), json!(task.progress_percent));
|
||||
payload.insert("progress_message".to_string(), json!(task.progress_message));
|
||||
payload.insert("video_url".to_string(), json!(task.video_url));
|
||||
payload.insert("video_urls".to_string(), serde_json::Value::Null);
|
||||
payload.insert("thumbnail_url".to_string(), serde_json::Value::Null);
|
||||
payload.insert("video_size_bytes".to_string(), serde_json::Value::Null);
|
||||
payload.insert(
|
||||
"video_duration_seconds".to_string(),
|
||||
serde_json::Value::Null,
|
||||
);
|
||||
payload.insert("video_expires_at".to_string(), serde_json::Value::Null);
|
||||
payload.insert("stored_video_path".to_string(), serde_json::Value::Null);
|
||||
payload.insert("storage_provider".to_string(), serde_json::Value::Null);
|
||||
payload.insert("error_code".to_string(), json!(task.error_code));
|
||||
payload.insert("error_message".to_string(), json!(task.error_message));
|
||||
payload.insert("retry_count".to_string(), json!(task.retry_count));
|
||||
payload.insert("max_retries".to_string(), serde_json::Value::Null);
|
||||
payload.insert(
|
||||
"poll_interval_seconds".to_string(),
|
||||
json!(task.poll_interval_seconds),
|
||||
);
|
||||
payload.insert(
|
||||
"next_poll_at".to_string(),
|
||||
json!(admin_video_task_timestamp(task.next_poll_at_unix_secs)),
|
||||
);
|
||||
payload.insert("poll_count".to_string(), json!(task.poll_count));
|
||||
payload.insert("max_poll_count".to_string(), json!(task.max_poll_count));
|
||||
payload.insert(
|
||||
"created_at".to_string(),
|
||||
json!(admin_video_task_timestamp(Some(task.created_at_unix_secs))),
|
||||
);
|
||||
payload.insert(
|
||||
"updated_at".to_string(),
|
||||
json!(admin_video_task_timestamp(Some(task.updated_at_unix_secs))),
|
||||
);
|
||||
payload.insert(
|
||||
"submitted_at".to_string(),
|
||||
json!(admin_video_task_timestamp(task.submitted_at_unix_secs)),
|
||||
);
|
||||
payload.insert(
|
||||
"completed_at".to_string(),
|
||||
json!(admin_video_task_timestamp(task.completed_at_unix_secs)),
|
||||
);
|
||||
payload.insert("request_metadata".to_string(), json!(task.request_metadata));
|
||||
|
||||
return Ok(Some(attach_admin_audit_response(
|
||||
Json(serde_json::Value::Object(payload)).into_response(),
|
||||
"admin_video_task_detail_viewed",
|
||||
"view_video_task_detail",
|
||||
"video_task",
|
||||
&task.id,
|
||||
)));
|
||||
};
|
||||
let Some(task) = async_task::read_video_task_detail(state, task_id).await? else {
|
||||
return Ok(Some(
|
||||
(
|
||||
http::StatusCode::NOT_FOUND,
|
||||
Json(json!({ "detail": "Video task not found" })),
|
||||
)
|
||||
.into_response(),
|
||||
));
|
||||
};
|
||||
if task
|
||||
.video_url
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
.is_none()
|
||||
{
|
||||
return Ok(Some(
|
||||
(
|
||||
http::StatusCode::NOT_FOUND,
|
||||
Json(json!({ "detail": "Video not available" })),
|
||||
)
|
||||
.into_response(),
|
||||
));
|
||||
}
|
||||
let Some(source) = async_task::read_video_task_video_source(state, task_id).await? else {
|
||||
return Ok(Some(
|
||||
(
|
||||
http::StatusCode::NOT_FOUND,
|
||||
Json(json!({ "detail": "Video not available" })),
|
||||
)
|
||||
.into_response(),
|
||||
));
|
||||
};
|
||||
return Ok(Some(attach_admin_audit_response(
|
||||
async_task::build_video_task_video_response(state, task_id, source).await?,
|
||||
"admin_video_task_video_viewed",
|
||||
"view_video_task_video",
|
||||
"video_task_video",
|
||||
task_id,
|
||||
)));
|
||||
}
|
||||
|
||||
Ok(None)
|
||||
}
|
||||
Reference in New Issue
Block a user