refactor: follow remote picture template policy
This commit is contained in:
@@ -237,8 +237,8 @@ backend = "boosapi"
|
||||
|
||||
### 图片助手模板
|
||||
|
||||
`/api/picture-agent/templates` 使用数据库缓存的远程 Linux Do 模板。服务启动时
|
||||
刷新一次,之后每 6 小时刷新;远程失败时继续使用旧缓存,不依赖 Redis。
|
||||
`/api/picture-agent/templates` 在请求时从远程 Linux Do 图库读取模板 JSON,不在本地
|
||||
捆绑数据文件;请求设置 30 秒超时和 16 MB 响应上限。
|
||||
|
||||
### SQLx vendor
|
||||
|
||||
|
||||
@@ -68,7 +68,7 @@
|
||||
- **Comfy Checkout 充值**:用户先在系统内创建充值记录,再跳转到配置的 Comfy Checkout 链接付款;充值记录会保留用于后续对账。
|
||||
- **Workflow UI 更新**:`bun run workflow-ui:install:cloud` 会安装当前 Comfy Cloud 前端资产并写入构建元数据,Rust 服务会对 JS/CSS 走 no-cache,避免继续显示旧版本。
|
||||
- **本地化时间**:前端展示时间优先使用浏览器/UA 时区,后端无时区时间按 UTC 解析后再转成本地显示。
|
||||
- **图片助手模板**:模板接口使用数据库缓存的远程图库数据,服务启动时刷新,之后每 6 小时刷新,远程失败时保留旧缓存。
|
||||
- **图片助手模板**:模板接口按请求读取远程图库数据,不在仓库中捆绑大体积 JSON。
|
||||
- **部署方式统一**:前端统一使用 Bun,生产构建统一使用 `./build-and-run.sh --production`。
|
||||
|
||||
## ✨ 核心功能
|
||||
|
||||
@@ -276,7 +276,6 @@ async fn main() -> anyhow::Result<()> {
|
||||
let state = AppState::new_with_redis(config.clone(), pool, builtin, runtime_auth, redis);
|
||||
let payment_job_worker = boosapi::services::payment_jobs::spawn(state.clone());
|
||||
let account_health_worker = boosapi::services::account_health::spawn(state.clone());
|
||||
let picture_template_worker = boosapi::routes::spawn_template_sync(state.clone());
|
||||
if let Err(err) = state.comfyui.start().await {
|
||||
tracing::warn!(%err, "ComfyUI embedded process did not start");
|
||||
}
|
||||
@@ -302,8 +301,6 @@ async fn main() -> anyhow::Result<()> {
|
||||
}
|
||||
account_health_worker.abort();
|
||||
let _ = account_health_worker.await;
|
||||
picture_template_worker.abort();
|
||||
let _ = picture_template_worker.await;
|
||||
|
||||
if let Err(err) = comfyui_process.stop().await {
|
||||
tracing::warn!(%err, "ComfyUI embedded process did not stop cleanly");
|
||||
|
||||
@@ -42,7 +42,6 @@ mod payment;
|
||||
mod payment_providers;
|
||||
mod payment_refunds;
|
||||
mod picture_agent;
|
||||
pub use picture_agent::spawn_template_sync;
|
||||
mod profile_avatar;
|
||||
mod proxy;
|
||||
mod search;
|
||||
|
||||
+14
-78
@@ -25,8 +25,6 @@ const TEMPLATE_GALLERY_PAGE_URL: &str =
|
||||
"https://weiyyin.github.io/linux-do-gpt-image/prompt_gallery.html";
|
||||
const TEMPLATE_DATA_URL: &str =
|
||||
"https://weiyyin.github.io/linux-do-gpt-image/linux_do_2036927_prompts_images.with_tags.json";
|
||||
const TEMPLATE_CACHE_KEY: &str = "picture_agent.linux_do_templates";
|
||||
const TEMPLATE_REFRESH_INTERVAL: Duration = Duration::from_secs(6 * 60 * 60);
|
||||
const TEMPLATE_REQUEST_TIMEOUT: Duration = Duration::from_secs(30);
|
||||
const TEMPLATE_MAX_BYTES: usize = 16 * 1024 * 1024;
|
||||
const TASK_TYPE_PICTURE_GENERATION: &str = "picture-agent-generation";
|
||||
@@ -62,21 +60,6 @@ pub fn routes() -> Router<AppState> {
|
||||
.route("/api/picture-agent/images/edits", post(images_edits))
|
||||
}
|
||||
|
||||
pub fn spawn_template_sync(state: AppState) -> tokio::task::JoinHandle<()> {
|
||||
tokio::spawn(async move {
|
||||
refresh_template_cache(&state).await;
|
||||
let mut ticker = tokio::time::interval_at(
|
||||
tokio::time::Instant::now() + TEMPLATE_REFRESH_INTERVAL,
|
||||
TEMPLATE_REFRESH_INTERVAL,
|
||||
);
|
||||
ticker.set_missed_tick_behavior(tokio::time::MissedTickBehavior::Skip);
|
||||
loop {
|
||||
ticker.tick().await;
|
||||
refresh_template_cache(&state).await;
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
#[derive(Debug, Deserialize)]
|
||||
struct TemplateQuery {
|
||||
limit: Option<usize>,
|
||||
@@ -454,64 +437,27 @@ async fn templates_handler(
|
||||
) -> AppResult<Json<Value>> {
|
||||
let _auth = resolve_picture_agent_auth(&headers, &state).await?;
|
||||
let limit = query.limit.unwrap_or(144).clamp(1, 240);
|
||||
let (templates, cache_status) = match cached_template_json(&state).await {
|
||||
Ok(Some(raw)) => match parse_gallery_templates(&raw, limit) {
|
||||
Ok(templates) => (templates, json!({ "ok": true, "source": "database-cache" })),
|
||||
Err(err) => (Vec::new(), json!({ "ok": false, "message": err })),
|
||||
},
|
||||
Ok(None) => {
|
||||
refresh_template_cache(&state).await;
|
||||
match cached_template_json(&state).await {
|
||||
Ok(Some(raw)) => match parse_gallery_templates(&raw, limit) {
|
||||
Ok(templates) => (
|
||||
templates,
|
||||
json!({ "ok": true, "source": "database-cache" }),
|
||||
),
|
||||
Err(err) => (Vec::new(), json!({ "ok": false, "message": err })),
|
||||
},
|
||||
Ok(None) => (
|
||||
Vec::new(),
|
||||
json!({ "ok": false, "message": "template cache is empty" }),
|
||||
),
|
||||
Err(err) => (Vec::new(), json!({ "ok": false, "message": err })),
|
||||
let (templates, remote_status) = match fetch_gallery_templates(&state.http, limit).await {
|
||||
Ok(templates) => (templates, json!({ "ok": true, "source": "remote-json" })),
|
||||
Err(err) => {
|
||||
tracing::warn!(%err, "Picture Agent remote Linux Do template gallery unavailable");
|
||||
(Vec::new(), json!({ "ok": false, "message": err }))
|
||||
}
|
||||
}
|
||||
Err(err) => (Vec::new(), json!({ "ok": false, "message": err })),
|
||||
};
|
||||
|
||||
Ok(Json(json!({
|
||||
"success": true,
|
||||
"sourceUrl": TEMPLATE_GALLERY_PAGE_URL,
|
||||
"source": cache_status,
|
||||
"source": remote_status,
|
||||
"templates": templates,
|
||||
})))
|
||||
}
|
||||
|
||||
async fn refresh_template_cache(state: &AppState) {
|
||||
match fetch_template_json(state).await {
|
||||
Ok(raw) => {
|
||||
let result = crate::db::sql::query(
|
||||
"INSERT INTO settings (key, value) VALUES (?, ?) ON CONFLICT (key) DO UPDATE SET value = EXCLUDED.value",
|
||||
)
|
||||
.bind(TEMPLATE_CACHE_KEY)
|
||||
.bind(raw)
|
||||
.execute(&*state.db)
|
||||
.await;
|
||||
if let Err(err) = result {
|
||||
tracing::warn!(%err, "failed to persist Picture Agent template cache");
|
||||
} else {
|
||||
tracing::info!("Picture Agent template cache refreshed");
|
||||
}
|
||||
}
|
||||
Err(err) => {
|
||||
tracing::warn!(%err, "Picture Agent template refresh failed; keeping old cache")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
async fn fetch_template_json(state: &AppState) -> Result<String, String> {
|
||||
let response = state
|
||||
.http
|
||||
async fn fetch_gallery_templates(
|
||||
client: &reqwest::Client,
|
||||
limit: usize,
|
||||
) -> Result<Vec<Value>, String> {
|
||||
let response = client
|
||||
.get(TEMPLATE_DATA_URL)
|
||||
.timeout(TEMPLATE_REQUEST_TIMEOUT)
|
||||
.send()
|
||||
@@ -528,19 +474,9 @@ async fn fetch_template_json(state: &AppState) -> Result<String, String> {
|
||||
"remote Linux Do templates exceed {TEMPLATE_MAX_BYTES} bytes"
|
||||
));
|
||||
}
|
||||
String::from_utf8(bytes.to_vec())
|
||||
.map_err(|err| format!("remote Linux Do templates are not UTF-8: {err}"))
|
||||
}
|
||||
|
||||
async fn cached_template_json(state: &AppState) -> Result<Option<String>, String> {
|
||||
crate::db::sql::query_scalar::<_, Option<String>>(
|
||||
"SELECT value FROM settings WHERE key = ? LIMIT 1",
|
||||
)
|
||||
.bind(TEMPLATE_CACHE_KEY)
|
||||
.fetch_optional(&*state.db)
|
||||
.await
|
||||
.map(|value| value.flatten())
|
||||
.map_err(|err| format!("failed to read Picture Agent template cache: {err}"))
|
||||
let raw = String::from_utf8(bytes.to_vec())
|
||||
.map_err(|err| format!("remote Linux Do templates are not UTF-8: {err}"))?;
|
||||
parse_gallery_templates(&raw, limit)
|
||||
}
|
||||
|
||||
fn parse_gallery_templates(raw: &str, limit: usize) -> Result<Vec<Value>, String> {
|
||||
|
||||
@@ -234,7 +234,7 @@ pub(super) async fn workflow_ui_index(State(state): State<AppState>) -> Html<Str
|
||||
<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>
|
||||
<p>官方 ComfyUI 前端会按后端类型从 <code>dist/workflow-ui-external</code> 或 <code>dist/workflow-ui-cloud</code> 加载。请先运行 <code>bun run workflow-ui:install</code>,然后刷新此页面。</p>
|
||||
</div>
|
||||
</div>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user