feat(enhance): discard, tests against a mock ComfyUI, docs (#192)
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
3a21761cfe
commit
97d9301a71
10 files changed
+1165
-48
No files matched your search
@@ -424,6 +424,94 @@ for the `link_versions` binary).
|
||||
against the current hash; `unresolved` rows are listed, never guessed. `--apply`
|
||||
takes a `VACUUM INTO` snapshot first.
|
||||
|
||||
### Enhance on demand (#192)
|
||||
|
||||
An **Enhance** action runs a ComfyUI workflow (a *template*) on one photo; the
|
||||
user previews the result in the app and **approves** it, and it lands in the
|
||||
library as a photo version (#191). Nothing reaches the library without an
|
||||
approval. Code: `src/enhance/` (`config.rs`, `path_map.rs`, `template.rs`,
|
||||
`comfy.rs`, `service.rs`, `handlers.rs`), table `enhance_jobs`
|
||||
(`src/database/enhance_dao.rs`). The apps only talk to ImageApi; ComfyUI has no
|
||||
auth and stays LAN-only, so ImageApi relays previews too.
|
||||
|
||||
- **Config: `enhance.toml`** (path from `ENHANCE_CONFIG_PATH`, else `./enhance.toml`;
|
||||
gitignored; `enhance.example.toml` is the annotated schema and a test parses it).
|
||||
No secrets live in it. Without `url` (or without the file) Enhance is off:
|
||||
`GET /enhance/templates` answers `available: false`. A malformed file fails startup;
|
||||
a *template* that doesn't validate is logged and skipped, the rest load.
|
||||
Keys: `url`, `timeout_secs`, `probe_ttl_secs`, `poll_secs`, `idle_free_secs`,
|
||||
`unreachable_grace_secs`, `default_template`, `warn_above_megapixels`,
|
||||
`[[path_map]]` (`from`, `to`), `[[template]]` (`name`, `label`, `description`,
|
||||
`file`, `prompt_mode`, `prompt_join`, `output_megapixels`).
|
||||
- **Templates** are API-format workflow exports, read-only (never edit them from
|
||||
here). Nodes are found by `_meta.title`: `imageapi:source` (required, gets the
|
||||
photo path), `imageapi:save` (required, forced to `require_approval = true`),
|
||||
`imageapi:prompt` (optional; fills the first of `text` / `prompt` / `value`),
|
||||
`imageapi:reference` (optional). `prompt_mode = "append"`: the user's text is joined
|
||||
after the template's own prompt with `prompt_join` (default one space); empty text
|
||||
leaves the prompt as is. `"replace"`: the text *is* the prompt and is required (empty
|
||||
= 400); it needs a prompt node. With no `reference_path` the reference node **and
|
||||
every `["<id>", n]` input linking to it** are removed (its leftover export path must
|
||||
never be used); if ComfyUI then rejects the prompt with `node_errors` the error says
|
||||
"this template needs a reference photo". Every `seed`, `noise_seed` and `*.seed`
|
||||
input gets a fresh random value per submit, or a retry would return ComfyUI's cached
|
||||
result. A stray `SaveImage` is only a startup warning.
|
||||
- **Path map**: library prefix <-> ComfyUI prefix (`/media/nas/` <-> `Z:\`), longest
|
||||
prefix wins, separators converted, case-insensitive on the ComfyUI side; used on
|
||||
submit and to recognise the path `/imageapi/approve` returns. No entries = identity.
|
||||
A mapped drive only exists in the logged-in session; use UNC if ComfyUI becomes a
|
||||
service.
|
||||
- **Jobs** (`enhance_jobs`, keyed by the submitted file's `content_hash`): status
|
||||
`queued | running | ready | approved | failed | expired | unavailable`. The poller
|
||||
(`EnhanceService::spawn`, every `poll_secs`, active jobs only) reads `/queue` then
|
||||
`/history/{id}`: in queue -> queued/running with `queue_position`; finished ->
|
||||
`ready` (staged results in `pending`) or `failed` (ComfyUI's message); known to
|
||||
neither -> **`expired`** (ComfyUI restarted and cleared its temp folder). An
|
||||
unreachable ComfyUI for `unreachable_grace_secs` -> `unavailable`. A preview or
|
||||
approve that finds the staged result gone also expires the job (HTTP 410).
|
||||
- **Idle `/free`**: every 30 s, if the queue is empty, none of our jobs is active and
|
||||
ComfyUI's newest `/history` entry has not changed for `idle_free_secs` (it includes
|
||||
hand-run workflows), `POST /free {unload_models, free_memory}` is sent once per quiet
|
||||
spell. "Not changed for" is measured on ImageApi's clock (no cross-host skew); the
|
||||
first observation after ImageApi starts counts as activity. `idle_free_secs = 0` = off.
|
||||
- **Approve** (`POST /enhance/jobs/{id}/approve`): ComfyUI's `/imageapi/approve` writes
|
||||
the file (ImageApi writes nothing under library roots). The returned path is mapped
|
||||
back, then a **targeted ingest** (`watcher::register_new_files` + `versions::link_new_files`)
|
||||
registers and links just that file, so it is in `/image/versions` before the response.
|
||||
Idempotent: an already-saved result returns its version without calling ComfyUI.
|
||||
- **Output size**: Turbo output is about 1 MP regardless of the original, so
|
||||
`output_megapixels` / `warn_above_megapixels` let clients warn above ~1.5 MP.
|
||||
|
||||
Endpoints (all need a bearer token; errors are `{"error": msg, "code": ...}`; `library`
|
||||
is accepted as a query or body field like the versions endpoints):
|
||||
|
||||
```
|
||||
GET /enhance/templates
|
||||
-> {"available": true, "reason"?: "ComfyUI isn't running" | "Enhance isn't configured",
|
||||
"default": "restore", "warn_above_megapixels": 1.5,
|
||||
"templates": [{"name","label","description","accepts_prompt",
|
||||
"prompt_mode": "append"|"replace", "prompt_required",
|
||||
"accepts_reference","default","output_megapixels",
|
||||
"warn_above_megapixels"}]}
|
||||
POST /enhance {"path","template","prompt"?,"reference_path"?,"library"?} -> job
|
||||
GET /enhance/jobs/{id} -> job
|
||||
GET /enhance/jobs?path=&library= -> {"jobs": [job, ...]} newest first, discarded hidden
|
||||
GET /enhance/jobs/{id}/preview/{n} -> the staged PNG (relayed from ComfyUI /view)
|
||||
POST /enhance/jobs/{id}/approve {"n"} -> a /image/versions `versions[]` entry
|
||||
POST /enhance/jobs/{id}/discard -> job (hidden from the list; ComfyUI untouched)
|
||||
|
||||
job = {"id","status","queue_position"?,"error"?,"path","library_id","template",
|
||||
"prompt"?,"reference_path"?,"created_at","updated_at",
|
||||
"pending": [{"n","preview": "/enhance/jobs/{id}/preview/{n}","approved"}],
|
||||
"saved": [{"n","path","library_id"}]}
|
||||
```
|
||||
|
||||
Error statuses: 400 bad request / empty required prompt / job not ready; 404 unknown
|
||||
template, photo or job; 410 `expired`; 422 ComfyUI rejected the workflow; 502 upstream
|
||||
or an approved file outside every library/path_map; 503 `unavailable` (ComfyUI down) or
|
||||
`disabled`. `pending` is empty unless the job is `ready`/`approved`. Approve answers 202
|
||||
`{"linked": false, ...}` if the file was saved but couldn't be ingested synchronously.
|
||||
|
||||
### File Processing Pipeline
|
||||
|
||||
**Thumbnail Generation:**
|
||||
@@ -632,6 +720,7 @@ GET /image/versions?path=...&library=... (path of the original or any version
|
||||
{ original:{path,library_id,hash}, versions:[{path,
|
||||
library_id,hash,kind,source,created_at,references,
|
||||
width,height}], display_hash })
|
||||
POST /enhance ... (Enhance on demand; see "Enhance on demand")
|
||||
PUT /image/versions/display (body { path, display_hash|null, library? }; global choice)
|
||||
// /photos hides versions behind their original (include_versions=true lists them) and adds
|
||||
// parallel arrays photo_display_paths / photo_version_counts; memories items, search hits and
|
||||
|
||||
Reference in new issue
Block a user