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)]
|
||||
struct UserDataQuery {
|
||||
dir: Option<String>,
|
||||
pub(super) struct UserDataQuery {
|
||||
pub(super) dir: Option<String>,
|
||||
#[serde(default)]
|
||||
split: bool,
|
||||
pub(super) split: bool,
|
||||
#[serde(default)]
|
||||
full_info: bool,
|
||||
pub(super) full_info: bool,
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
@@ -124,8 +124,7 @@ use auth::{auth_caller, caller_from_headers, caller_is_admin, caller_user_id};
|
||||
mod node_defs;
|
||||
use node_defs::{
|
||||
core_node_definitions, core_workflow_template_index, default_boosapi_workflow_graph,
|
||||
inject_boosapi_workflow_template_payload, node_type_def_to_comfy, normalize_prompt_payload,
|
||||
text_to_video_workflow_graph, workflow_template_payload,
|
||||
node_type_def_to_comfy, normalize_prompt_payload, text_to_video_workflow_graph,
|
||||
};
|
||||
mod models;
|
||||
use models::{
|
||||
@@ -138,6 +137,12 @@ use assets::{
|
||||
embedded_workflow_ui_for_config, official_workflow_template_asset, workflow_cloud_api_key,
|
||||
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> {
|
||||
Router::new()
|
||||
@@ -230,11 +235,11 @@ fn default_history_limit() -> i64 {
|
||||
50
|
||||
}
|
||||
|
||||
fn safe_view_path(value: &str) -> String {
|
||||
pub(super) fn safe_view_path(value: &str) -> String {
|
||||
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")
|
||||
.map(PathBuf::from)
|
||||
.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!(
|
||||
input.map(|v| v.trim().to_ascii_lowercase()).as_deref(),
|
||||
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],
|
||||
include_compat_core: bool,
|
||||
) -> Map<String, Value> {
|
||||
@@ -390,7 +395,7 @@ fn workflow_node_defs_with_models(
|
||||
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 {
|
||||
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()
|
||||
}
|
||||
|
||||
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(
|
||||
State(state): State<AppState>,
|
||||
headers: HeaderMap,
|
||||
@@ -2716,7 +2438,7 @@ fn native_workflow_enabled(state: &AppState) -> bool {
|
||||
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() {
|
||||
Duration::from_secs(21600)
|
||||
} 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() {
|
||||
Duration::from_secs(120)
|
||||
} else {
|
||||
@@ -2804,7 +2526,7 @@ async fn native_workflow_json(
|
||||
Ok(Json(value))
|
||||
}
|
||||
|
||||
async fn native_workflow_json_cached(
|
||||
pub(super) async fn native_workflow_json_cached(
|
||||
state: &AppState,
|
||||
method: Method,
|
||||
path: &str,
|
||||
|
||||
Reference in New Issue
Block a user