refactor(workflow_ui): 抽出 assets 子模块
Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,311 @@
|
||||
use super::{
|
||||
cache_control_for_path, content_type_for_path, default_boosapi_workflow_graph,
|
||||
embedded_workflow_ui_response, inject_bootstrap, local_workflow_template_asset,
|
||||
native_workflow_base, simple_urlencode, text_to_video_workflow_graph,
|
||||
workflow_template_not_found_response, EMBEDDED_WORKFLOW_UI, EMBEDDED_WORKFLOW_UI_CLOUD,
|
||||
EMBEDDED_WORKFLOW_UI_EXTERNAL, OFFICIAL_WORKFLOW_TEMPLATES_BASE,
|
||||
};
|
||||
use crate::error::{AppError, AppResult};
|
||||
use crate::AppState;
|
||||
use axum::{
|
||||
extract::{Path, State},
|
||||
http::{header, StatusCode},
|
||||
response::{Html, IntoResponse, Response},
|
||||
Json,
|
||||
};
|
||||
use include_dir::Dir;
|
||||
use serde_json::{json, Value};
|
||||
use std::path::{Path as StdPath, PathBuf};
|
||||
|
||||
pub(super) fn workflow_ui_root_for_config(config: &crate::config::ComfyUIConfig) -> PathBuf {
|
||||
if let Ok(path) = std::env::var("BOOSAPI_WORKFLOW_UI_DIR") {
|
||||
return resolve_workflow_ui_dir(PathBuf::from(path));
|
||||
}
|
||||
let preferred = resolve_workflow_ui_dir(if config.uses_cloud_backend() {
|
||||
std::env::var("BOOSAPI_WORKFLOW_UI_CLOUD_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| PathBuf::from("dist/workflow-ui-cloud"))
|
||||
} else if config.uses_native_backend() {
|
||||
std::env::var("BOOSAPI_WORKFLOW_UI_EXTERNAL_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| PathBuf::from("dist/workflow-ui-external"))
|
||||
} else {
|
||||
PathBuf::from("dist/workflow-ui")
|
||||
});
|
||||
if workflow_ui_dir_is_usable(&preferred) {
|
||||
preferred
|
||||
} else {
|
||||
resolve_workflow_ui_dir(PathBuf::from("dist/workflow-ui"))
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn resolve_workflow_ui_dir(path: PathBuf) -> PathBuf {
|
||||
if path.is_absolute() || workflow_ui_dir_is_usable(&path) {
|
||||
return path;
|
||||
}
|
||||
let manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(&path);
|
||||
if workflow_ui_dir_is_usable(&manifest_path) {
|
||||
manifest_path
|
||||
} else {
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn embedded_workflow_ui_for_config(
|
||||
config: &crate::config::ComfyUIConfig,
|
||||
) -> &'static Dir<'static> {
|
||||
if config.uses_cloud_backend() && embedded_workflow_ui_is_usable(&EMBEDDED_WORKFLOW_UI_CLOUD) {
|
||||
&EMBEDDED_WORKFLOW_UI_CLOUD
|
||||
} else if config.uses_native_backend()
|
||||
&& embedded_workflow_ui_is_usable(&EMBEDDED_WORKFLOW_UI_EXTERNAL)
|
||||
{
|
||||
&EMBEDDED_WORKFLOW_UI_EXTERNAL
|
||||
} else {
|
||||
&EMBEDDED_WORKFLOW_UI
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) fn workflow_ui_dir_is_usable(dir: &PathBuf) -> bool {
|
||||
let index = dir.join("index.html");
|
||||
std::fs::read_to_string(index)
|
||||
.map(|html| !workflow_ui_index_is_placeholder(&html))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub(super) fn embedded_workflow_ui_is_usable(dir: &Dir<'static>) -> bool {
|
||||
dir.get_file("index.html")
|
||||
.and_then(|file| std::str::from_utf8(file.contents()).ok())
|
||||
.map(|html| !workflow_ui_index_is_placeholder(html))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
pub(super) fn workflow_ui_index_is_placeholder(html: &str) -> bool {
|
||||
html.contains("Workflow UI not built")
|
||||
}
|
||||
|
||||
pub(super) async fn workflow_ui_runtime(State(state): State<AppState>) -> Json<Value> {
|
||||
let mode = if state.config.comfyui.uses_docker_backend() {
|
||||
"docker"
|
||||
} else if state.config.comfyui.uses_cloud_backend() {
|
||||
"cloud"
|
||||
} else if state.config.comfyui.uses_runninghub_backend() {
|
||||
"runninghub"
|
||||
} else if state.config.comfyui.uses_native_backend() {
|
||||
"external"
|
||||
} else {
|
||||
"boosapi"
|
||||
};
|
||||
let frontend_dir = workflow_ui_root_for_config(&state.config.comfyui);
|
||||
Json(json!({
|
||||
"mode": mode,
|
||||
"baseUrl": state.config.comfyui.target_base_url(),
|
||||
"frontendDir": frontend_dir,
|
||||
"frontendBuild": workflow_ui_build_metadata(&frontend_dir),
|
||||
"cloudApiKeyConfigured": workflow_cloud_api_key(&state.config.comfyui).is_some(),
|
||||
}))
|
||||
}
|
||||
|
||||
pub(super) fn workflow_ui_build_metadata(dir: &StdPath) -> Value {
|
||||
let path = dir.join("boosapi-workflow-ui.json");
|
||||
std::fs::read_to_string(path)
|
||||
.ok()
|
||||
.and_then(|raw| serde_json::from_str::<Value>(&raw).ok())
|
||||
.unwrap_or(Value::Null)
|
||||
}
|
||||
|
||||
pub(super) fn workflow_cloud_api_key(config: &crate::config::ComfyUIConfig) -> Option<&str> {
|
||||
config
|
||||
.api_key
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
pub(super) async fn workflow_ui_index(State(state): State<AppState>) -> Html<String> {
|
||||
let dir = workflow_ui_root_for_config(&state.config.comfyui);
|
||||
let index = dir.join("index.html");
|
||||
match tokio::fs::read_to_string(&index).await {
|
||||
Ok(html) => Html(inject_bootstrap(html, &state.config.comfyui)),
|
||||
Err(_) => {
|
||||
if let Some(file) =
|
||||
embedded_workflow_ui_for_config(&state.config.comfyui).get_file("index.html")
|
||||
{
|
||||
let html = String::from_utf8_lossy(file.contents()).to_string();
|
||||
return Html(inject_bootstrap(html, &state.config.comfyui));
|
||||
}
|
||||
Html(
|
||||
r#"<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BoosAPI Workflow Studio</title>
|
||||
<style>
|
||||
html, body { margin: 0; width: 100%; height: 100%; background: #111827; color: #e5e7eb; font-family: system-ui, sans-serif; }
|
||||
.wrap { display: grid; place-items: center; height: 100%; padding: 24px; text-align: center; }
|
||||
.box { max-width: 720px; line-height: 1.7; }
|
||||
code { background: rgba(255,255,255,0.08); padding: 2px 6px; border-radius: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="box">
|
||||
<h1>Workflow Studio 前端未构建</h1>
|
||||
<p>官方 ComfyUI 前端会按后端类型从 <code>dist/workflow-ui-external</code> 或 <code>dist/workflow-ui-cloud</code> 加载。请先运行 <code>npm run workflow-ui:install</code>,然后刷新此页面。</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>"#
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(super) async fn workflow_ui_static(
|
||||
State(state): State<AppState>,
|
||||
Path(path): Path<String>,
|
||||
) -> AppResult<Response> {
|
||||
let dir = workflow_ui_root_for_config(&state.config.comfyui);
|
||||
let path = path.trim_start_matches('/').to_string();
|
||||
if path == "api/workflow_templates/boosapi/文生图示例.json" {
|
||||
return Ok(Json(default_boosapi_workflow_graph()).into_response());
|
||||
}
|
||||
if path == "api/workflow_templates/boosapi/文生视频示例.json" {
|
||||
return Ok(Json(text_to_video_workflow_graph()).into_response());
|
||||
}
|
||||
let file_path = if path.is_empty() {
|
||||
dir.join("index.html")
|
||||
} else {
|
||||
dir.join(&path)
|
||||
};
|
||||
|
||||
if file_path.is_dir() {
|
||||
return serve_file(file_path.join("index.html")).await;
|
||||
}
|
||||
if tokio::fs::metadata(&file_path).await.is_ok() {
|
||||
return serve_file(file_path).await;
|
||||
}
|
||||
if let Some(response) =
|
||||
embedded_workflow_ui_response(&state.config.comfyui, &path, path.is_empty())
|
||||
{
|
||||
return Ok(response);
|
||||
}
|
||||
if PathBuf::from(&path).extension().is_none() {
|
||||
return Ok(workflow_ui_index(State(state)).await.into_response());
|
||||
}
|
||||
serve_file(file_path).await
|
||||
}
|
||||
|
||||
pub(super) async fn official_workflow_template_asset(
|
||||
State(state): State<AppState>,
|
||||
Path(path): Path<String>,
|
||||
) -> AppResult<Response> {
|
||||
let path = path.trim_start_matches('/').replace('\\', "/");
|
||||
if path.is_empty() || path.contains("..") {
|
||||
return Err(AppError::BadRequest("invalid template path".into()));
|
||||
}
|
||||
if !state.config.comfyui.uses_cloud_backend() {
|
||||
if let Some(response) = local_workflow_template_asset(&path) {
|
||||
return Ok(response);
|
||||
}
|
||||
}
|
||||
let encoded_path = path
|
||||
.split('/')
|
||||
.map(simple_urlencode)
|
||||
.collect::<Vec<_>>()
|
||||
.join("/");
|
||||
let url = if state.config.comfyui.uses_cloud_backend() {
|
||||
format!(
|
||||
"{}/templates/{}",
|
||||
native_workflow_base(&state),
|
||||
encoded_path
|
||||
)
|
||||
} else {
|
||||
format!("{}/{}", OFFICIAL_WORKFLOW_TEMPLATES_BASE, encoded_path)
|
||||
};
|
||||
let mut request =
|
||||
crate::template_http::client(&state.config, state.config.proxy.upstream_timeout_secs)
|
||||
.get(url);
|
||||
if state.config.comfyui.uses_cloud_backend() {
|
||||
if let Some(api_key) = state
|
||||
.config
|
||||
.comfyui
|
||||
.api_key
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
request = request
|
||||
.header("X-API-Key", api_key)
|
||||
.header("Authorization", format!("Bearer {api_key}"));
|
||||
}
|
||||
}
|
||||
let response = match request.send().await {
|
||||
Ok(response) => response,
|
||||
Err(err) => {
|
||||
tracing::warn!(%err, path, "workflow template remote source unavailable");
|
||||
if let Some(response) = local_workflow_template_asset(&path) {
|
||||
return Ok(response);
|
||||
}
|
||||
return Ok(workflow_template_not_found_response(&path));
|
||||
}
|
||||
};
|
||||
let status = response.status();
|
||||
let bytes = match response.bytes().await {
|
||||
Ok(bytes) => bytes,
|
||||
Err(err) => {
|
||||
tracing::warn!(%err, path, "workflow template remote body unavailable");
|
||||
if let Some(response) = local_workflow_template_asset(&path) {
|
||||
return Ok(response);
|
||||
}
|
||||
return Ok(workflow_template_not_found_response(&path));
|
||||
}
|
||||
};
|
||||
Ok((
|
||||
StatusCode::from_u16(status.as_u16()).unwrap_or(StatusCode::BAD_GATEWAY),
|
||||
[
|
||||
(
|
||||
header::CONTENT_TYPE,
|
||||
content_type_for_path(&PathBuf::from(&path)),
|
||||
),
|
||||
(
|
||||
header::CACHE_CONTROL,
|
||||
if path == "index.json" {
|
||||
"public, max-age=1800"
|
||||
} else {
|
||||
"public, max-age=86400"
|
||||
},
|
||||
),
|
||||
],
|
||||
bytes,
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
|
||||
pub(super) async fn serve_file(path: PathBuf) -> AppResult<Response> {
|
||||
match tokio::fs::read(&path).await {
|
||||
Ok(bytes) => {
|
||||
let content_type = content_type_for_path(&path);
|
||||
let cache_control = cache_control_for_path(&path);
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
[
|
||||
(axum::http::header::CONTENT_TYPE, content_type),
|
||||
(axum::http::header::CACHE_CONTROL, cache_control),
|
||||
],
|
||||
bytes,
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
Err(_) => Ok((
|
||||
StatusCode::NOT_FOUND,
|
||||
[(
|
||||
axum::http::header::CONTENT_TYPE,
|
||||
"text/plain; charset=utf-8",
|
||||
)],
|
||||
"workflow-ui asset not found".to_string(),
|
||||
)
|
||||
.into_response()),
|
||||
}
|
||||
}
|
||||
@@ -133,6 +133,11 @@ use models::{
|
||||
native_folder_models_from_object_info, workflow_folder_has_route_models,
|
||||
workflow_folder_is_api_only, workflow_folder_route_model_items, workflow_route_models,
|
||||
};
|
||||
mod assets;
|
||||
use assets::{
|
||||
embedded_workflow_ui_for_config, official_workflow_template_asset, workflow_cloud_api_key,
|
||||
workflow_ui_index, workflow_ui_runtime, workflow_ui_static,
|
||||
};
|
||||
|
||||
pub fn routes() -> Router<AppState> {
|
||||
Router::new()
|
||||
@@ -221,108 +226,6 @@ pub fn routes() -> Router<AppState> {
|
||||
.layer(DefaultBodyLimit::max(WORKFLOW_UPLOAD_BODY_LIMIT_BYTES))
|
||||
}
|
||||
|
||||
fn workflow_ui_root_for_config(config: &crate::config::ComfyUIConfig) -> PathBuf {
|
||||
if let Ok(path) = std::env::var("BOOSAPI_WORKFLOW_UI_DIR") {
|
||||
return resolve_workflow_ui_dir(PathBuf::from(path));
|
||||
}
|
||||
let preferred = resolve_workflow_ui_dir(if config.uses_cloud_backend() {
|
||||
std::env::var("BOOSAPI_WORKFLOW_UI_CLOUD_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| PathBuf::from("dist/workflow-ui-cloud"))
|
||||
} else if config.uses_native_backend() {
|
||||
std::env::var("BOOSAPI_WORKFLOW_UI_EXTERNAL_DIR")
|
||||
.map(PathBuf::from)
|
||||
.unwrap_or_else(|_| PathBuf::from("dist/workflow-ui-external"))
|
||||
} else {
|
||||
PathBuf::from("dist/workflow-ui")
|
||||
});
|
||||
if workflow_ui_dir_is_usable(&preferred) {
|
||||
preferred
|
||||
} else {
|
||||
resolve_workflow_ui_dir(PathBuf::from("dist/workflow-ui"))
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_workflow_ui_dir(path: PathBuf) -> PathBuf {
|
||||
if path.is_absolute() || workflow_ui_dir_is_usable(&path) {
|
||||
return path;
|
||||
}
|
||||
let manifest_path = PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(&path);
|
||||
if workflow_ui_dir_is_usable(&manifest_path) {
|
||||
manifest_path
|
||||
} else {
|
||||
path
|
||||
}
|
||||
}
|
||||
|
||||
fn embedded_workflow_ui_for_config(config: &crate::config::ComfyUIConfig) -> &'static Dir<'static> {
|
||||
if config.uses_cloud_backend() && embedded_workflow_ui_is_usable(&EMBEDDED_WORKFLOW_UI_CLOUD) {
|
||||
&EMBEDDED_WORKFLOW_UI_CLOUD
|
||||
} else if config.uses_native_backend()
|
||||
&& embedded_workflow_ui_is_usable(&EMBEDDED_WORKFLOW_UI_EXTERNAL)
|
||||
{
|
||||
&EMBEDDED_WORKFLOW_UI_EXTERNAL
|
||||
} else {
|
||||
&EMBEDDED_WORKFLOW_UI
|
||||
}
|
||||
}
|
||||
|
||||
fn workflow_ui_dir_is_usable(dir: &PathBuf) -> bool {
|
||||
let index = dir.join("index.html");
|
||||
std::fs::read_to_string(index)
|
||||
.map(|html| !workflow_ui_index_is_placeholder(&html))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn embedded_workflow_ui_is_usable(dir: &Dir<'static>) -> bool {
|
||||
dir.get_file("index.html")
|
||||
.and_then(|file| std::str::from_utf8(file.contents()).ok())
|
||||
.map(|html| !workflow_ui_index_is_placeholder(html))
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
fn workflow_ui_index_is_placeholder(html: &str) -> bool {
|
||||
html.contains("Workflow UI not built")
|
||||
}
|
||||
|
||||
async fn workflow_ui_runtime(State(state): State<AppState>) -> Json<Value> {
|
||||
let mode = if state.config.comfyui.uses_docker_backend() {
|
||||
"docker"
|
||||
} else if state.config.comfyui.uses_cloud_backend() {
|
||||
"cloud"
|
||||
} else if state.config.comfyui.uses_runninghub_backend() {
|
||||
"runninghub"
|
||||
} else if state.config.comfyui.uses_native_backend() {
|
||||
"external"
|
||||
} else {
|
||||
"boosapi"
|
||||
};
|
||||
let frontend_dir = workflow_ui_root_for_config(&state.config.comfyui);
|
||||
Json(json!({
|
||||
"mode": mode,
|
||||
"baseUrl": state.config.comfyui.target_base_url(),
|
||||
"frontendDir": frontend_dir,
|
||||
"frontendBuild": workflow_ui_build_metadata(&frontend_dir),
|
||||
"cloudApiKeyConfigured": workflow_cloud_api_key(&state.config.comfyui).is_some(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn workflow_ui_build_metadata(dir: &StdPath) -> Value {
|
||||
let path = dir.join("boosapi-workflow-ui.json");
|
||||
std::fs::read_to_string(path)
|
||||
.ok()
|
||||
.and_then(|raw| serde_json::from_str::<Value>(&raw).ok())
|
||||
.unwrap_or(Value::Null)
|
||||
}
|
||||
|
||||
fn workflow_cloud_api_key(config: &crate::config::ComfyUIConfig) -> Option<&str> {
|
||||
config
|
||||
.api_key
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
}
|
||||
|
||||
fn default_history_limit() -> i64 {
|
||||
50
|
||||
}
|
||||
@@ -687,168 +590,6 @@ fn prompt_contains_boosapi_nodes(prompt: &Value) -> bool {
|
||||
.unwrap_or(false)
|
||||
}
|
||||
|
||||
async fn workflow_ui_index(State(state): State<AppState>) -> Html<String> {
|
||||
let dir = workflow_ui_root_for_config(&state.config.comfyui);
|
||||
let index = dir.join("index.html");
|
||||
match tokio::fs::read_to_string(&index).await {
|
||||
Ok(html) => Html(inject_bootstrap(html, &state.config.comfyui)),
|
||||
Err(_) => {
|
||||
if let Some(file) =
|
||||
embedded_workflow_ui_for_config(&state.config.comfyui).get_file("index.html")
|
||||
{
|
||||
let html = String::from_utf8_lossy(file.contents()).to_string();
|
||||
return Html(inject_bootstrap(html, &state.config.comfyui));
|
||||
}
|
||||
Html(
|
||||
r#"<!DOCTYPE html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<title>BoosAPI Workflow Studio</title>
|
||||
<style>
|
||||
html, body { margin: 0; width: 100%; height: 100%; background: #111827; color: #e5e7eb; font-family: system-ui, sans-serif; }
|
||||
.wrap { display: grid; place-items: center; height: 100%; padding: 24px; text-align: center; }
|
||||
.box { max-width: 720px; line-height: 1.7; }
|
||||
code { background: rgba(255,255,255,0.08); padding: 2px 6px; border-radius: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div class="wrap">
|
||||
<div class="box">
|
||||
<h1>Workflow Studio 前端未构建</h1>
|
||||
<p>官方 ComfyUI 前端会按后端类型从 <code>dist/workflow-ui-external</code> 或 <code>dist/workflow-ui-cloud</code> 加载。请先运行 <code>npm run workflow-ui:install</code>,然后刷新此页面。</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
</html>"#
|
||||
.to_string(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn workflow_ui_static(
|
||||
State(state): State<AppState>,
|
||||
Path(path): Path<String>,
|
||||
) -> AppResult<Response> {
|
||||
let dir = workflow_ui_root_for_config(&state.config.comfyui);
|
||||
let path = path.trim_start_matches('/').to_string();
|
||||
if path == "api/workflow_templates/boosapi/文生图示例.json" {
|
||||
return Ok(Json(default_boosapi_workflow_graph()).into_response());
|
||||
}
|
||||
if path == "api/workflow_templates/boosapi/文生视频示例.json" {
|
||||
return Ok(Json(text_to_video_workflow_graph()).into_response());
|
||||
}
|
||||
let file_path = if path.is_empty() {
|
||||
dir.join("index.html")
|
||||
} else {
|
||||
dir.join(&path)
|
||||
};
|
||||
|
||||
if file_path.is_dir() {
|
||||
return serve_file(file_path.join("index.html")).await;
|
||||
}
|
||||
if tokio::fs::metadata(&file_path).await.is_ok() {
|
||||
return serve_file(file_path).await;
|
||||
}
|
||||
if let Some(response) =
|
||||
embedded_workflow_ui_response(&state.config.comfyui, &path, path.is_empty())
|
||||
{
|
||||
return Ok(response);
|
||||
}
|
||||
if PathBuf::from(&path).extension().is_none() {
|
||||
return Ok(workflow_ui_index(State(state)).await.into_response());
|
||||
}
|
||||
serve_file(file_path).await
|
||||
}
|
||||
|
||||
async fn official_workflow_template_asset(
|
||||
State(state): State<AppState>,
|
||||
Path(path): Path<String>,
|
||||
) -> AppResult<Response> {
|
||||
let path = path.trim_start_matches('/').replace('\\', "/");
|
||||
if path.is_empty() || path.contains("..") {
|
||||
return Err(AppError::BadRequest("invalid template path".into()));
|
||||
}
|
||||
if !state.config.comfyui.uses_cloud_backend() {
|
||||
if let Some(response) = local_workflow_template_asset(&path) {
|
||||
return Ok(response);
|
||||
}
|
||||
}
|
||||
let encoded_path = path
|
||||
.split('/')
|
||||
.map(simple_urlencode)
|
||||
.collect::<Vec<_>>()
|
||||
.join("/");
|
||||
let url = if state.config.comfyui.uses_cloud_backend() {
|
||||
format!(
|
||||
"{}/templates/{}",
|
||||
native_workflow_base(&state),
|
||||
encoded_path
|
||||
)
|
||||
} else {
|
||||
format!("{}/{}", OFFICIAL_WORKFLOW_TEMPLATES_BASE, encoded_path)
|
||||
};
|
||||
let mut request =
|
||||
crate::template_http::client(&state.config, state.config.proxy.upstream_timeout_secs)
|
||||
.get(url);
|
||||
if state.config.comfyui.uses_cloud_backend() {
|
||||
if let Some(api_key) = state
|
||||
.config
|
||||
.comfyui
|
||||
.api_key
|
||||
.as_deref()
|
||||
.map(str::trim)
|
||||
.filter(|value| !value.is_empty())
|
||||
{
|
||||
request = request
|
||||
.header("X-API-Key", api_key)
|
||||
.header("Authorization", format!("Bearer {api_key}"));
|
||||
}
|
||||
}
|
||||
let response = match request.send().await {
|
||||
Ok(response) => response,
|
||||
Err(err) => {
|
||||
tracing::warn!(%err, path, "workflow template remote source unavailable");
|
||||
if let Some(response) = local_workflow_template_asset(&path) {
|
||||
return Ok(response);
|
||||
}
|
||||
return Ok(workflow_template_not_found_response(&path));
|
||||
}
|
||||
};
|
||||
let status = response.status();
|
||||
let bytes = match response.bytes().await {
|
||||
Ok(bytes) => bytes,
|
||||
Err(err) => {
|
||||
tracing::warn!(%err, path, "workflow template remote body unavailable");
|
||||
if let Some(response) = local_workflow_template_asset(&path) {
|
||||
return Ok(response);
|
||||
}
|
||||
return Ok(workflow_template_not_found_response(&path));
|
||||
}
|
||||
};
|
||||
Ok((
|
||||
StatusCode::from_u16(status.as_u16()).unwrap_or(StatusCode::BAD_GATEWAY),
|
||||
[
|
||||
(
|
||||
header::CONTENT_TYPE,
|
||||
content_type_for_path(&PathBuf::from(&path)),
|
||||
),
|
||||
(
|
||||
header::CACHE_CONTROL,
|
||||
if path == "index.json" {
|
||||
"public, max-age=1800"
|
||||
} else {
|
||||
"public, max-age=86400"
|
||||
},
|
||||
),
|
||||
],
|
||||
bytes,
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
|
||||
fn local_workflow_template_asset(path: &str) -> Option<Response> {
|
||||
match path {
|
||||
"index.json" | "index.zh.json" | "index.zh-CN.json" | "index_logo.json" => Some(
|
||||
@@ -968,33 +709,6 @@ fn embedded_workflow_ui_response(
|
||||
)
|
||||
}
|
||||
|
||||
async fn serve_file(path: PathBuf) -> AppResult<Response> {
|
||||
match tokio::fs::read(&path).await {
|
||||
Ok(bytes) => {
|
||||
let content_type = content_type_for_path(&path);
|
||||
let cache_control = cache_control_for_path(&path);
|
||||
Ok((
|
||||
StatusCode::OK,
|
||||
[
|
||||
(axum::http::header::CONTENT_TYPE, content_type),
|
||||
(axum::http::header::CACHE_CONTROL, cache_control),
|
||||
],
|
||||
bytes,
|
||||
)
|
||||
.into_response())
|
||||
}
|
||||
Err(_) => Ok((
|
||||
StatusCode::NOT_FOUND,
|
||||
[(
|
||||
axum::http::header::CONTENT_TYPE,
|
||||
"text/plain; charset=utf-8",
|
||||
)],
|
||||
"workflow-ui asset not found".to_string(),
|
||||
)
|
||||
.into_response()),
|
||||
}
|
||||
}
|
||||
|
||||
fn content_type_for_path(path: &StdPath) -> &'static str {
|
||||
match path
|
||||
.extension()
|
||||
|
||||
Reference in New Issue
Block a user