refactor(workflow_ui): 抽出 comfy_proxy 子模块
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,321 @@
|
|||||||
|
use super::auth::{auth_caller, caller_is_admin, caller_user_id};
|
||||||
|
use super::models::workflow_route_models;
|
||||||
|
use super::node_defs::{inject_boosapi_workflow_template_payload, workflow_template_payload};
|
||||||
|
use super::{
|
||||||
|
auth_caller_optional, inject_boosapi_object_info, native_workflow_json_cached,
|
||||||
|
native_workflow_metadata_ttl, native_workflow_status_ttl, parse_boolish, safe_view_path,
|
||||||
|
user_home, workflow_node_defs_with_models, UserDataQuery, WorkflowCaller,
|
||||||
|
};
|
||||||
|
use crate::{
|
||||||
|
error::{AppError, AppResult},
|
||||||
|
AppState,
|
||||||
|
};
|
||||||
|
use axum::{
|
||||||
|
extract::{Path, Query, State},
|
||||||
|
http::{header, HeaderMap, Method, StatusCode},
|
||||||
|
response::{IntoResponse, Response},
|
||||||
|
Json,
|
||||||
|
};
|
||||||
|
use serde_json::{json, Value};
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
pub(super) async fn get_user_config(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
) -> AppResult<Json<Value>> {
|
||||||
|
let caller = auth_caller(&state, &headers).await?;
|
||||||
|
let audience = match caller {
|
||||||
|
WorkflowCaller::Admin => "admin",
|
||||||
|
WorkflowCaller::User { .. } => "user",
|
||||||
|
};
|
||||||
|
Ok(Json(json!({
|
||||||
|
"username": audience,
|
||||||
|
"role": audience,
|
||||||
|
"id": caller_user_id(&caller),
|
||||||
|
})))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn create_user(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
) -> AppResult<Response> {
|
||||||
|
let _ = auth_caller(&state, &headers).await?;
|
||||||
|
Ok((
|
||||||
|
StatusCode::METHOD_NOT_ALLOWED,
|
||||||
|
Json(json!({ "error": "read-only" })),
|
||||||
|
)
|
||||||
|
.into_response())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn get_settings(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
) -> AppResult<Json<Value>> {
|
||||||
|
let _ = auth_caller(&state, &headers).await?;
|
||||||
|
if state.config.comfyui.uses_cloud_backend() {
|
||||||
|
return Ok(Json(json!({
|
||||||
|
"Comfy.Workflow.ShowMissingModelsWarning": false
|
||||||
|
})));
|
||||||
|
}
|
||||||
|
Ok(Json(json!({})))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn get_setting(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Path(id): Path<String>,
|
||||||
|
) -> AppResult<Json<Value>> {
|
||||||
|
let _ = auth_caller(&state, &headers).await?;
|
||||||
|
if state.config.comfyui.uses_cloud_backend() && id == "Comfy.Workflow.ShowMissingModelsWarning"
|
||||||
|
{
|
||||||
|
return Ok(Json(Value::Bool(false)));
|
||||||
|
}
|
||||||
|
Ok(Json(Value::Null))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn store_settings(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Json(_body): Json<Value>,
|
||||||
|
) -> AppResult<Json<Value>> {
|
||||||
|
let _ = auth_caller(&state, &headers).await?;
|
||||||
|
Ok(Json(json!({ "success": true })))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn store_setting(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Path(_id): Path<String>,
|
||||||
|
Json(_body): Json<Value>,
|
||||||
|
) -> AppResult<Json<Value>> {
|
||||||
|
let _ = auth_caller(&state, &headers).await?;
|
||||||
|
Ok(Json(json!({ "success": true })))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn userdata_list(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Query(query): Query<UserDataQuery>,
|
||||||
|
) -> AppResult<Response> {
|
||||||
|
let caller =
|
||||||
|
auth_caller_optional(&state, &headers).unwrap_or(WorkflowCaller::User { user_id: 0 });
|
||||||
|
let user_id = caller_user_id(&caller);
|
||||||
|
let dir = query.dir.unwrap_or_else(|| "workflow".to_string());
|
||||||
|
let base = user_home(user_id).join("userdata").join(&dir);
|
||||||
|
if tokio::fs::metadata(&base).await.is_err() {
|
||||||
|
return Ok(Json(json!([])).into_response());
|
||||||
|
}
|
||||||
|
if query.full_info {
|
||||||
|
let mut entries = Vec::new();
|
||||||
|
let mut reader = match tokio::fs::read_dir(&base).await {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(_) => return Ok(Json(json!([])).into_response()),
|
||||||
|
};
|
||||||
|
while let Ok(Some(entry)) = reader.next_entry().await {
|
||||||
|
let path = entry.path();
|
||||||
|
let meta = match entry.metadata().await {
|
||||||
|
Ok(meta) => meta,
|
||||||
|
Err(_) => continue,
|
||||||
|
};
|
||||||
|
entries.push(json!({
|
||||||
|
"path": path.to_string_lossy().to_string(),
|
||||||
|
"name": entry.file_name().to_string_lossy().to_string(),
|
||||||
|
"type": if meta.is_dir() { "directory" } else { "file" },
|
||||||
|
"size": meta.len(),
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
return Ok(Json(Value::Array(entries)).into_response());
|
||||||
|
}
|
||||||
|
let mut items = Vec::new();
|
||||||
|
let mut reader = match tokio::fs::read_dir(&base).await {
|
||||||
|
Ok(value) => value,
|
||||||
|
Err(_) => return Ok(Json(json!([])).into_response()),
|
||||||
|
};
|
||||||
|
while let Ok(Some(entry)) = reader.next_entry().await {
|
||||||
|
let name = entry.file_name().to_string_lossy().to_string();
|
||||||
|
if query.split {
|
||||||
|
items.push(json!([name.clone()]));
|
||||||
|
} else {
|
||||||
|
items.push(Value::String(name));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(Json(Value::Array(items)).into_response())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn userdata_get(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Path(file): Path<String>,
|
||||||
|
) -> AppResult<Response> {
|
||||||
|
let caller = auth_caller(&state, &headers).await?;
|
||||||
|
let root = user_home(caller_user_id(&caller)).join("userdata");
|
||||||
|
let path = root.join(safe_view_path(&file));
|
||||||
|
let bytes = tokio::fs::read(&path)
|
||||||
|
.await
|
||||||
|
.map_err(|_| AppError::NotFound("User data file not found".into()))?;
|
||||||
|
Ok((
|
||||||
|
StatusCode::OK,
|
||||||
|
[(header::CONTENT_TYPE, "application/octet-stream")],
|
||||||
|
bytes,
|
||||||
|
)
|
||||||
|
.into_response())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn userdata_store(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Path(file): Path<String>,
|
||||||
|
Query(query): Query<HashMap<String, String>>,
|
||||||
|
body: axum::body::Bytes,
|
||||||
|
) -> AppResult<Response> {
|
||||||
|
let caller = auth_caller(&state, &headers).await?;
|
||||||
|
let root = user_home(caller_user_id(&caller)).join("userdata");
|
||||||
|
let path = root.join(safe_view_path(&file));
|
||||||
|
if let Some(parent) = path.parent() {
|
||||||
|
tokio::fs::create_dir_all(parent)
|
||||||
|
.await
|
||||||
|
.map_err(|err| AppError::Internal(anyhow::anyhow!(err)))?;
|
||||||
|
}
|
||||||
|
tokio::fs::write(&path, body)
|
||||||
|
.await
|
||||||
|
.map_err(|err| AppError::Internal(anyhow::anyhow!(err)))?;
|
||||||
|
Ok(Json(json!({
|
||||||
|
"name": file,
|
||||||
|
"subfolder": query.get("subfolder").cloned().unwrap_or_default(),
|
||||||
|
"type": query.get("type").cloned().unwrap_or_else(|| "input".to_string())
|
||||||
|
}))
|
||||||
|
.into_response())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn userdata_delete(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Path(file): Path<String>,
|
||||||
|
) -> AppResult<Response> {
|
||||||
|
let caller = auth_caller(&state, &headers).await?;
|
||||||
|
let root = user_home(caller_user_id(&caller)).join("userdata");
|
||||||
|
let path = root.join(safe_view_path(&file));
|
||||||
|
let _ = tokio::fs::remove_file(&path).await;
|
||||||
|
Ok(StatusCode::NO_CONTENT.into_response())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn move_userdata(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
Path((source, dest)): Path<(String, String)>,
|
||||||
|
Query(query): Query<HashMap<String, String>>,
|
||||||
|
) -> AppResult<Response> {
|
||||||
|
let caller = auth_caller(&state, &headers).await?;
|
||||||
|
let root = user_home(caller_user_id(&caller)).join("userdata");
|
||||||
|
let src = root.join(safe_view_path(&source));
|
||||||
|
let dst = root.join(safe_view_path(&dest));
|
||||||
|
if let Some(parent) = dst.parent() {
|
||||||
|
tokio::fs::create_dir_all(parent)
|
||||||
|
.await
|
||||||
|
.map_err(|err| AppError::Internal(anyhow::anyhow!(err)))?;
|
||||||
|
}
|
||||||
|
let overwrite = parse_boolish(query.get("overwrite").map(String::as_str));
|
||||||
|
if !overwrite && tokio::fs::metadata(&dst).await.is_ok() {
|
||||||
|
return Err(AppError::BadRequest("destination already exists".into()));
|
||||||
|
}
|
||||||
|
tokio::fs::rename(&src, &dst)
|
||||||
|
.await
|
||||||
|
.map_err(|err| AppError::Internal(anyhow::anyhow!(err)))?;
|
||||||
|
Ok(Json(json!({ "success": true })).into_response())
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn extensions(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
) -> AppResult<Json<Value>> {
|
||||||
|
let _ = auth_caller_optional(&state, &headers);
|
||||||
|
Ok(Json(json!(["extensions/core"])))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn workflow_templates(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
) -> AppResult<Json<Value>> {
|
||||||
|
let _ = auth_caller_optional(&state, &headers);
|
||||||
|
if state.config.comfyui.uses_cloud_backend() {
|
||||||
|
let mut value = native_workflow_json_cached(
|
||||||
|
&state,
|
||||||
|
Method::GET,
|
||||||
|
"/workflow_templates",
|
||||||
|
None,
|
||||||
|
native_workflow_metadata_ttl(&state),
|
||||||
|
)
|
||||||
|
.await
|
||||||
|
.map(|value| value.0)
|
||||||
|
.unwrap_or_else(|_| json!({}));
|
||||||
|
inject_boosapi_workflow_template_payload(&mut value);
|
||||||
|
return Ok(Json(value));
|
||||||
|
}
|
||||||
|
Ok(Json(workflow_template_payload()))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn embeddings(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
) -> AppResult<Json<Value>> {
|
||||||
|
let _ = auth_caller_optional(&state, &headers);
|
||||||
|
Ok(Json(json!([])))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn object_info(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
) -> AppResult<Json<Value>> {
|
||||||
|
let _ = auth_caller_optional(&state, &headers);
|
||||||
|
let route_models = workflow_route_models(&state).await.unwrap_or_default();
|
||||||
|
if state.config.comfyui.uses_native_backend() {
|
||||||
|
let mut value = native_workflow_json_cached(
|
||||||
|
&state,
|
||||||
|
Method::GET,
|
||||||
|
"/object_info",
|
||||||
|
None,
|
||||||
|
native_workflow_metadata_ttl(&state),
|
||||||
|
)
|
||||||
|
.await?
|
||||||
|
.0;
|
||||||
|
inject_boosapi_object_info(&mut value, &route_models);
|
||||||
|
return Ok(Json(value));
|
||||||
|
}
|
||||||
|
Ok(Json(Value::Object(workflow_node_defs_with_models(
|
||||||
|
&route_models,
|
||||||
|
true,
|
||||||
|
))))
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(super) async fn system_stats(
|
||||||
|
State(state): State<AppState>,
|
||||||
|
headers: HeaderMap,
|
||||||
|
) -> AppResult<Json<Value>> {
|
||||||
|
if state.config.comfyui.uses_native_backend() {
|
||||||
|
let _ = auth_caller_optional(&state, &headers);
|
||||||
|
return native_workflow_json_cached(
|
||||||
|
&state,
|
||||||
|
Method::GET,
|
||||||
|
"/system_stats",
|
||||||
|
None,
|
||||||
|
native_workflow_status_ttl(&state),
|
||||||
|
)
|
||||||
|
.await;
|
||||||
|
}
|
||||||
|
let caller =
|
||||||
|
auth_caller_optional(&state, &headers).unwrap_or(WorkflowCaller::User { user_id: 0 });
|
||||||
|
let host = if state.config.server.host == "0.0.0.0" || state.config.server.host == "::" {
|
||||||
|
"127.0.0.1"
|
||||||
|
} else {
|
||||||
|
state.config.server.host.as_str()
|
||||||
|
};
|
||||||
|
Ok(Json(json!({
|
||||||
|
"system": {
|
||||||
|
"host": host,
|
||||||
|
"port": state.config.server.port,
|
||||||
|
"role": if caller_is_admin(&caller) { "admin" } else { "user" }
|
||||||
|
},
|
||||||
|
"devices": [],
|
||||||
|
"queue_remaining": 0,
|
||||||
|
})))
|
||||||
|
}
|
||||||
+20
-298
@@ -90,12 +90,12 @@ struct ViewQuery {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
struct UserDataQuery {
|
pub(super) struct UserDataQuery {
|
||||||
dir: Option<String>,
|
pub(super) dir: Option<String>,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
split: bool,
|
pub(super) split: bool,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
full_info: bool,
|
pub(super) full_info: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Deserialize)]
|
#[derive(Debug, Deserialize)]
|
||||||
@@ -124,8 +124,7 @@ use auth::{auth_caller, caller_from_headers, caller_is_admin, caller_user_id};
|
|||||||
mod node_defs;
|
mod node_defs;
|
||||||
use node_defs::{
|
use node_defs::{
|
||||||
core_node_definitions, core_workflow_template_index, default_boosapi_workflow_graph,
|
core_node_definitions, core_workflow_template_index, default_boosapi_workflow_graph,
|
||||||
inject_boosapi_workflow_template_payload, node_type_def_to_comfy, normalize_prompt_payload,
|
node_type_def_to_comfy, normalize_prompt_payload, text_to_video_workflow_graph,
|
||||||
text_to_video_workflow_graph, workflow_template_payload,
|
|
||||||
};
|
};
|
||||||
mod models;
|
mod models;
|
||||||
use models::{
|
use models::{
|
||||||
@@ -138,6 +137,12 @@ use assets::{
|
|||||||
embedded_workflow_ui_for_config, official_workflow_template_asset, workflow_cloud_api_key,
|
embedded_workflow_ui_for_config, official_workflow_template_asset, workflow_cloud_api_key,
|
||||||
workflow_ui_index, workflow_ui_runtime, workflow_ui_static,
|
workflow_ui_index, workflow_ui_runtime, workflow_ui_static,
|
||||||
};
|
};
|
||||||
|
mod comfy_proxy;
|
||||||
|
use comfy_proxy::{
|
||||||
|
create_user, embeddings, extensions, get_setting, get_settings, get_user_config, move_userdata,
|
||||||
|
object_info, store_setting, store_settings, system_stats, userdata_delete, userdata_get,
|
||||||
|
userdata_list, userdata_store, workflow_templates,
|
||||||
|
};
|
||||||
|
|
||||||
pub fn routes() -> Router<AppState> {
|
pub fn routes() -> Router<AppState> {
|
||||||
Router::new()
|
Router::new()
|
||||||
@@ -230,11 +235,11 @@ fn default_history_limit() -> i64 {
|
|||||||
50
|
50
|
||||||
}
|
}
|
||||||
|
|
||||||
fn safe_view_path(value: &str) -> String {
|
pub(super) fn safe_view_path(value: &str) -> String {
|
||||||
value.trim().replace('\\', "/")
|
value.trim().replace('\\', "/")
|
||||||
}
|
}
|
||||||
|
|
||||||
fn user_home(user_id: Option<i64>) -> PathBuf {
|
pub(super) fn user_home(user_id: Option<i64>) -> PathBuf {
|
||||||
let base = std::env::var("BOOSAPI_WORKSPACE_DATA_ROOT")
|
let base = std::env::var("BOOSAPI_WORKSPACE_DATA_ROOT")
|
||||||
.map(PathBuf::from)
|
.map(PathBuf::from)
|
||||||
.unwrap_or_else(|_| PathBuf::from("/data/boosapi-workspace"));
|
.unwrap_or_else(|_| PathBuf::from("/data/boosapi-workspace"));
|
||||||
@@ -285,7 +290,7 @@ fn workspace_kind_from_mime(mime_type: &str) -> Option<&'static str> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn parse_boolish(input: Option<&str>) -> bool {
|
pub(super) fn parse_boolish(input: Option<&str>) -> bool {
|
||||||
matches!(
|
matches!(
|
||||||
input.map(|v| v.trim().to_ascii_lowercase()).as_deref(),
|
input.map(|v| v.trim().to_ascii_lowercase()).as_deref(),
|
||||||
Some("1" | "true" | "yes" | "on")
|
Some("1" | "true" | "yes" | "on")
|
||||||
@@ -370,7 +375,7 @@ fn is_boosapi_extension_node_type(node_type: &str) -> bool {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn workflow_node_defs_with_models(
|
pub(super) fn workflow_node_defs_with_models(
|
||||||
models: &[String],
|
models: &[String],
|
||||||
include_compat_core: bool,
|
include_compat_core: bool,
|
||||||
) -> Map<String, Value> {
|
) -> Map<String, Value> {
|
||||||
@@ -390,7 +395,7 @@ fn workflow_node_defs_with_models(
|
|||||||
defs
|
defs
|
||||||
}
|
}
|
||||||
|
|
||||||
fn inject_boosapi_object_info(value: &mut Value, models: &[String]) {
|
pub(super) fn inject_boosapi_object_info(value: &mut Value, models: &[String]) {
|
||||||
let Some(object) = value.as_object_mut() else {
|
let Some(object) = value.as_object_mut() else {
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@@ -743,293 +748,10 @@ fn cache_control_for_path(path: &StdPath) -> &'static str {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn auth_caller_optional(state: &AppState, headers: &HeaderMap) -> Option<WorkflowCaller> {
|
pub(super) fn auth_caller_optional(state: &AppState, headers: &HeaderMap) -> Option<WorkflowCaller> {
|
||||||
caller_from_headers(state, headers).ok()
|
caller_from_headers(state, headers).ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn get_user_config(
|
|
||||||
State(state): State<AppState>,
|
|
||||||
headers: HeaderMap,
|
|
||||||
) -> AppResult<Json<Value>> {
|
|
||||||
let caller = auth_caller(&state, &headers).await?;
|
|
||||||
let audience = match caller {
|
|
||||||
WorkflowCaller::Admin => "admin",
|
|
||||||
WorkflowCaller::User { .. } => "user",
|
|
||||||
};
|
|
||||||
Ok(Json(json!({
|
|
||||||
"username": audience,
|
|
||||||
"role": audience,
|
|
||||||
"id": caller_user_id(&caller),
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn create_user(State(state): State<AppState>, headers: HeaderMap) -> AppResult<Response> {
|
|
||||||
let _ = auth_caller(&state, &headers).await?;
|
|
||||||
Ok((
|
|
||||||
StatusCode::METHOD_NOT_ALLOWED,
|
|
||||||
Json(json!({ "error": "read-only" })),
|
|
||||||
)
|
|
||||||
.into_response())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_settings(State(state): State<AppState>, headers: HeaderMap) -> AppResult<Json<Value>> {
|
|
||||||
let _ = auth_caller(&state, &headers).await?;
|
|
||||||
if state.config.comfyui.uses_cloud_backend() {
|
|
||||||
return Ok(Json(json!({
|
|
||||||
"Comfy.Workflow.ShowMissingModelsWarning": false
|
|
||||||
})));
|
|
||||||
}
|
|
||||||
Ok(Json(json!({})))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn get_setting(
|
|
||||||
State(state): State<AppState>,
|
|
||||||
headers: HeaderMap,
|
|
||||||
Path(id): Path<String>,
|
|
||||||
) -> AppResult<Json<Value>> {
|
|
||||||
let _ = auth_caller(&state, &headers).await?;
|
|
||||||
if state.config.comfyui.uses_cloud_backend() && id == "Comfy.Workflow.ShowMissingModelsWarning"
|
|
||||||
{
|
|
||||||
return Ok(Json(Value::Bool(false)));
|
|
||||||
}
|
|
||||||
Ok(Json(Value::Null))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn store_settings(
|
|
||||||
State(state): State<AppState>,
|
|
||||||
headers: HeaderMap,
|
|
||||||
Json(_body): Json<Value>,
|
|
||||||
) -> AppResult<Json<Value>> {
|
|
||||||
let _ = auth_caller(&state, &headers).await?;
|
|
||||||
Ok(Json(json!({ "success": true })))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn store_setting(
|
|
||||||
State(state): State<AppState>,
|
|
||||||
headers: HeaderMap,
|
|
||||||
Path(_id): Path<String>,
|
|
||||||
Json(_body): Json<Value>,
|
|
||||||
) -> AppResult<Json<Value>> {
|
|
||||||
let _ = auth_caller(&state, &headers).await?;
|
|
||||||
Ok(Json(json!({ "success": true })))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn userdata_list(
|
|
||||||
State(state): State<AppState>,
|
|
||||||
headers: HeaderMap,
|
|
||||||
Query(query): Query<UserDataQuery>,
|
|
||||||
) -> AppResult<Response> {
|
|
||||||
let caller =
|
|
||||||
auth_caller_optional(&state, &headers).unwrap_or(WorkflowCaller::User { user_id: 0 });
|
|
||||||
let user_id = caller_user_id(&caller);
|
|
||||||
let dir = query.dir.unwrap_or_else(|| "workflow".to_string());
|
|
||||||
let base = user_home(user_id).join("userdata").join(&dir);
|
|
||||||
if tokio::fs::metadata(&base).await.is_err() {
|
|
||||||
return Ok(Json(json!([])).into_response());
|
|
||||||
}
|
|
||||||
if query.full_info {
|
|
||||||
let mut entries = Vec::new();
|
|
||||||
let mut reader = match tokio::fs::read_dir(&base).await {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(_) => return Ok(Json(json!([])).into_response()),
|
|
||||||
};
|
|
||||||
while let Ok(Some(entry)) = reader.next_entry().await {
|
|
||||||
let path = entry.path();
|
|
||||||
let meta = match entry.metadata().await {
|
|
||||||
Ok(meta) => meta,
|
|
||||||
Err(_) => continue,
|
|
||||||
};
|
|
||||||
entries.push(json!({
|
|
||||||
"path": path.to_string_lossy().to_string(),
|
|
||||||
"name": entry.file_name().to_string_lossy().to_string(),
|
|
||||||
"type": if meta.is_dir() { "directory" } else { "file" },
|
|
||||||
"size": meta.len(),
|
|
||||||
}));
|
|
||||||
}
|
|
||||||
return Ok(Json(Value::Array(entries)).into_response());
|
|
||||||
}
|
|
||||||
let mut items = Vec::new();
|
|
||||||
let mut reader = match tokio::fs::read_dir(&base).await {
|
|
||||||
Ok(value) => value,
|
|
||||||
Err(_) => return Ok(Json(json!([])).into_response()),
|
|
||||||
};
|
|
||||||
while let Ok(Some(entry)) = reader.next_entry().await {
|
|
||||||
let name = entry.file_name().to_string_lossy().to_string();
|
|
||||||
if query.split {
|
|
||||||
items.push(json!([name.clone()]));
|
|
||||||
} else {
|
|
||||||
items.push(Value::String(name));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Ok(Json(Value::Array(items)).into_response())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn userdata_get(
|
|
||||||
State(state): State<AppState>,
|
|
||||||
headers: HeaderMap,
|
|
||||||
Path(file): Path<String>,
|
|
||||||
) -> AppResult<Response> {
|
|
||||||
let caller = auth_caller(&state, &headers).await?;
|
|
||||||
let root = user_home(caller_user_id(&caller)).join("userdata");
|
|
||||||
let path = root.join(safe_view_path(&file));
|
|
||||||
let bytes = tokio::fs::read(&path)
|
|
||||||
.await
|
|
||||||
.map_err(|_| AppError::NotFound("User data file not found".into()))?;
|
|
||||||
Ok((
|
|
||||||
StatusCode::OK,
|
|
||||||
[(header::CONTENT_TYPE, "application/octet-stream")],
|
|
||||||
bytes,
|
|
||||||
)
|
|
||||||
.into_response())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn userdata_store(
|
|
||||||
State(state): State<AppState>,
|
|
||||||
headers: HeaderMap,
|
|
||||||
Path(file): Path<String>,
|
|
||||||
Query(query): Query<HashMap<String, String>>,
|
|
||||||
body: axum::body::Bytes,
|
|
||||||
) -> AppResult<Response> {
|
|
||||||
let caller = auth_caller(&state, &headers).await?;
|
|
||||||
let root = user_home(caller_user_id(&caller)).join("userdata");
|
|
||||||
let path = root.join(safe_view_path(&file));
|
|
||||||
if let Some(parent) = path.parent() {
|
|
||||||
tokio::fs::create_dir_all(parent)
|
|
||||||
.await
|
|
||||||
.map_err(|err| AppError::Internal(anyhow::anyhow!(err)))?;
|
|
||||||
}
|
|
||||||
tokio::fs::write(&path, body)
|
|
||||||
.await
|
|
||||||
.map_err(|err| AppError::Internal(anyhow::anyhow!(err)))?;
|
|
||||||
Ok(Json(json!({
|
|
||||||
"name": file,
|
|
||||||
"subfolder": query.get("subfolder").cloned().unwrap_or_default(),
|
|
||||||
"type": query.get("type").cloned().unwrap_or_else(|| "input".to_string())
|
|
||||||
}))
|
|
||||||
.into_response())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn userdata_delete(
|
|
||||||
State(state): State<AppState>,
|
|
||||||
headers: HeaderMap,
|
|
||||||
Path(file): Path<String>,
|
|
||||||
) -> AppResult<Response> {
|
|
||||||
let caller = auth_caller(&state, &headers).await?;
|
|
||||||
let root = user_home(caller_user_id(&caller)).join("userdata");
|
|
||||||
let path = root.join(safe_view_path(&file));
|
|
||||||
let _ = tokio::fs::remove_file(&path).await;
|
|
||||||
Ok(StatusCode::NO_CONTENT.into_response())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn move_userdata(
|
|
||||||
State(state): State<AppState>,
|
|
||||||
headers: HeaderMap,
|
|
||||||
Path((source, dest)): Path<(String, String)>,
|
|
||||||
Query(query): Query<HashMap<String, String>>,
|
|
||||||
) -> AppResult<Response> {
|
|
||||||
let caller = auth_caller(&state, &headers).await?;
|
|
||||||
let root = user_home(caller_user_id(&caller)).join("userdata");
|
|
||||||
let src = root.join(safe_view_path(&source));
|
|
||||||
let dst = root.join(safe_view_path(&dest));
|
|
||||||
if let Some(parent) = dst.parent() {
|
|
||||||
tokio::fs::create_dir_all(parent)
|
|
||||||
.await
|
|
||||||
.map_err(|err| AppError::Internal(anyhow::anyhow!(err)))?;
|
|
||||||
}
|
|
||||||
let overwrite = parse_boolish(query.get("overwrite").map(String::as_str));
|
|
||||||
if !overwrite && tokio::fs::metadata(&dst).await.is_ok() {
|
|
||||||
return Err(AppError::BadRequest("destination already exists".into()));
|
|
||||||
}
|
|
||||||
tokio::fs::rename(&src, &dst)
|
|
||||||
.await
|
|
||||||
.map_err(|err| AppError::Internal(anyhow::anyhow!(err)))?;
|
|
||||||
Ok(Json(json!({ "success": true })).into_response())
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn extensions(State(state): State<AppState>, headers: HeaderMap) -> AppResult<Json<Value>> {
|
|
||||||
let _ = auth_caller_optional(&state, &headers);
|
|
||||||
Ok(Json(json!(["extensions/core"])))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn workflow_templates(
|
|
||||||
State(state): State<AppState>,
|
|
||||||
headers: HeaderMap,
|
|
||||||
) -> AppResult<Json<Value>> {
|
|
||||||
let _ = auth_caller_optional(&state, &headers);
|
|
||||||
if state.config.comfyui.uses_cloud_backend() {
|
|
||||||
let mut value = native_workflow_json_cached(
|
|
||||||
&state,
|
|
||||||
Method::GET,
|
|
||||||
"/workflow_templates",
|
|
||||||
None,
|
|
||||||
native_workflow_metadata_ttl(&state),
|
|
||||||
)
|
|
||||||
.await
|
|
||||||
.map(|value| value.0)
|
|
||||||
.unwrap_or_else(|_| json!({}));
|
|
||||||
inject_boosapi_workflow_template_payload(&mut value);
|
|
||||||
return Ok(Json(value));
|
|
||||||
}
|
|
||||||
Ok(Json(workflow_template_payload()))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn embeddings(State(state): State<AppState>, headers: HeaderMap) -> AppResult<Json<Value>> {
|
|
||||||
let _ = auth_caller_optional(&state, &headers);
|
|
||||||
Ok(Json(json!([])))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn object_info(State(state): State<AppState>, headers: HeaderMap) -> AppResult<Json<Value>> {
|
|
||||||
let _ = auth_caller_optional(&state, &headers);
|
|
||||||
let route_models = workflow_route_models(&state).await.unwrap_or_default();
|
|
||||||
if state.config.comfyui.uses_native_backend() {
|
|
||||||
let mut value = native_workflow_json_cached(
|
|
||||||
&state,
|
|
||||||
Method::GET,
|
|
||||||
"/object_info",
|
|
||||||
None,
|
|
||||||
native_workflow_metadata_ttl(&state),
|
|
||||||
)
|
|
||||||
.await?
|
|
||||||
.0;
|
|
||||||
inject_boosapi_object_info(&mut value, &route_models);
|
|
||||||
return Ok(Json(value));
|
|
||||||
}
|
|
||||||
Ok(Json(Value::Object(workflow_node_defs_with_models(
|
|
||||||
&route_models,
|
|
||||||
true,
|
|
||||||
))))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn system_stats(State(state): State<AppState>, headers: HeaderMap) -> AppResult<Json<Value>> {
|
|
||||||
if state.config.comfyui.uses_native_backend() {
|
|
||||||
let _ = auth_caller_optional(&state, &headers);
|
|
||||||
return native_workflow_json_cached(
|
|
||||||
&state,
|
|
||||||
Method::GET,
|
|
||||||
"/system_stats",
|
|
||||||
None,
|
|
||||||
native_workflow_status_ttl(&state),
|
|
||||||
)
|
|
||||||
.await;
|
|
||||||
}
|
|
||||||
let caller =
|
|
||||||
auth_caller_optional(&state, &headers).unwrap_or(WorkflowCaller::User { user_id: 0 });
|
|
||||||
let host = if state.config.server.host == "0.0.0.0" || state.config.server.host == "::" {
|
|
||||||
"127.0.0.1"
|
|
||||||
} else {
|
|
||||||
state.config.server.host.as_str()
|
|
||||||
};
|
|
||||||
Ok(Json(json!({
|
|
||||||
"system": {
|
|
||||||
"host": host,
|
|
||||||
"port": state.config.server.port,
|
|
||||||
"role": if caller_is_admin(&caller) { "admin" } else { "user" }
|
|
||||||
},
|
|
||||||
"devices": [],
|
|
||||||
"queue_remaining": 0,
|
|
||||||
})))
|
|
||||||
}
|
|
||||||
|
|
||||||
async fn workflow_cache_status(
|
async fn workflow_cache_status(
|
||||||
State(state): State<AppState>,
|
State(state): State<AppState>,
|
||||||
headers: HeaderMap,
|
headers: HeaderMap,
|
||||||
@@ -2716,7 +2438,7 @@ fn native_workflow_enabled(state: &AppState) -> bool {
|
|||||||
state.config.comfyui.uses_native_backend()
|
state.config.comfyui.uses_native_backend()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn native_workflow_metadata_ttl(state: &AppState) -> Duration {
|
pub(super) fn native_workflow_metadata_ttl(state: &AppState) -> Duration {
|
||||||
if state.config.comfyui.uses_runninghub_backend() {
|
if state.config.comfyui.uses_runninghub_backend() {
|
||||||
Duration::from_secs(21600)
|
Duration::from_secs(21600)
|
||||||
} else {
|
} else {
|
||||||
@@ -2724,7 +2446,7 @@ fn native_workflow_metadata_ttl(state: &AppState) -> Duration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn native_workflow_status_ttl(state: &AppState) -> Duration {
|
pub(super) fn native_workflow_status_ttl(state: &AppState) -> Duration {
|
||||||
if state.config.comfyui.uses_runninghub_backend() {
|
if state.config.comfyui.uses_runninghub_backend() {
|
||||||
Duration::from_secs(120)
|
Duration::from_secs(120)
|
||||||
} else {
|
} else {
|
||||||
@@ -2804,7 +2526,7 @@ async fn native_workflow_json(
|
|||||||
Ok(Json(value))
|
Ok(Json(value))
|
||||||
}
|
}
|
||||||
|
|
||||||
async fn native_workflow_json_cached(
|
pub(super) async fn native_workflow_json_cached(
|
||||||
state: &AppState,
|
state: &AppState,
|
||||||
method: Method,
|
method: Method,
|
||||||
path: &str,
|
path: &str,
|
||||||
|
|||||||
Reference in New Issue
Block a user