updates UI

This commit is contained in:
mike
2026-07-01 02:25:51 +02:00
parent 66685684c1
commit 145fa686e4
22 changed files with 1559 additions and 159 deletions

View File

@@ -50,9 +50,13 @@ WORKFLOW_PATH = os.environ.get(
"WORKFLOW_PATH",
os.path.join(os.path.dirname(os.path.abspath(__file__)), "workflow_qwen_edit.json"),
)
# Default target pixel area for the output latent. The MI50 is not fast, so we
# cap at ~1MP by default; raise via MAX_AREA env if you want bigger output.
MAX_AREA = int(os.environ.get("MAX_AREA", str(1024 * 1024)))
# Default target pixel area for the output latent.
# We currently cap at ~1MP by default; raise via MAX_AREA env if you want bigger output.
# A6000 48GB is not VRAM-bound here, so default to a ~2MP output budget.
# This comfortably allows full-HD-ish outputs like 1920x1080.
# Override via MAX_AREA when needed.
#export MAX_AREA="${MAX_AREA:-2097152}"
MAX_AREA = int(os.environ.get("MAX_AREA", str(2097152)))
GEN_TIMEOUT = int(os.environ.get("GEN_TIMEOUT", "600")) # seconds per request
# Node ids in workflow_qwen_edit.json (kept stable on purpose).
@@ -1758,6 +1762,23 @@ def _batch_worker(job_id: str, filenames: list, prompts: list[str], poses: list,
if jobs[job_id].get("cancelled"):
return
try:
try:
database.save_db_prompt("pose-prompt", prompt, {
"pose": pose,
"seed": seed,
"max_area": max_area,
"wireframe_ref": wireframe_ref,
"wireframe_time": wireframe_time,
"pad_top": pad_top,
"pad_right": pad_right,
"pad_bottom": pad_bottom,
"pad_left": pad_left,
"pad_fill": pad_fill,
"pad_outpaint": pad_outpaint
})
except Exception as db_err:
print(f"[batch] failed to save to prompt table: {db_err}")
pil = base_pil
actual_prompt = prompt
if pad_outpaint:
@@ -1865,6 +1886,21 @@ def _multi_ref_worker(job_id: str, filenames: list[str], prompts: list[str], pos
for prompt, pose in zip(prompts, poses):
try:
try:
database.save_db_prompt("pose-prompt", prompt, {
"pose": pose,
"seed": seed,
"max_area": max_area,
"filenames": filenames,
"pad_top": pad_top,
"pad_right": pad_right,
"pad_bottom": pad_bottom,
"pad_left": pad_left,
"pad_fill": pad_fill,
"pad_outpaint": pad_outpaint
})
except Exception as db_err:
print(f"[multi-ref] failed to save to prompt table: {db_err}")
work_pil = primary_pil
actual_prompt = prompt
if pad_outpaint:
@@ -1947,6 +1983,23 @@ def update_config(update: ConfigUpdate):
return {"seed": conf["seed"]}
class SavePromptRequest(BaseModel):
type: str
prompt_text: str
metadata: dict | None = None
@app.post("/prompts")
def api_save_prompt(req: SavePromptRequest):
database.save_db_prompt(req.type, req.prompt_text, req.metadata)
return {"status": "success"}
@app.get("/prompts")
def api_list_prompts(type: str | None = None, limit: int = 100):
return database.list_db_prompts(type, limit)
class GroupArchiveRequest(BaseModel):
filenames: list[str]
@@ -2123,12 +2176,173 @@ def refine_prompt(req: RefineRequest):
r.raise_for_status()
data = r.json()
refined = data["choices"][0]["message"]["content"].strip()
try:
database.save_db_prompt("refine", refined, {
"original": req.prompt,
"filename": req.filename
})
except Exception as db_err:
print(f"[refine-prompt] failed to save to prompt table: {db_err}")
return {"refined": refined}
except Exception as e:
print(f"Refinement error: {e}")
raise HTTPException(500, f"LLM refinement failed: {str(e)}")
class UpdatePromptRequest(BaseModel):
prompt: str
@app.post("/images/{filename:path}/reverse-engineer")
def reverse_engineer(filename: str):
person = database.get_person(filename)
if not person:
raise HTTPException(404, "Image not found in database")
# Extract metadata on the fly if pose_description is not present
if person[15] is None:
try:
metadata = _process_image_for_metadata(filename)
if metadata:
# Reload person row
person = database.get_person(filename)
except Exception as e:
print(f"Failed to process image for metadata during reverse-engineer: {e}")
# Build context string
tags_val = person[2]
clip_desc_val = person[4]
original_prompt = person[6]
pose_desc = person[15]
people_count = person[17]
anatomical_completeness = person[18]
facial_direction = person[19]
objects_val = person[20]
context_parts = []
# 1. Base prompt or tags
if original_prompt:
context_parts.append(f"Original Prompt/Tags: {original_prompt}")
elif clip_desc_val:
context_parts.append(f"Scene Tags Description: {clip_desc_val}")
# 2. WD Tagger tags
if tags_val:
try:
if isinstance(tags_val, str):
tags_list = json.loads(tags_val)
else:
tags_list = tags_val
if tags_list:
tag_names = [t["tag"] for t in tags_list if isinstance(t, dict) and "tag" in t and t.get("score", 0) > 0.35]
if tag_names:
context_parts.append(f"WD Tagger tags: {', '.join(tag_names[:25])}")
except Exception:
pass
# 3. Pose description
if pose_desc:
context_parts.append(f"Pose details: {pose_desc}")
# 4. People count
if people_count is not None:
context_parts.append(f"Subject count: {people_count} person(s)")
# 5. Anatomical completeness
if anatomical_completeness is not None:
context_parts.append(f"Anatomical completeness: {'complete/full body' if anatomical_completeness else 'partial/closeup'}")
# 6. Gaze direction
if facial_direction:
context_parts.append(f"Gaze and facial direction: {facial_direction}")
# 7. Scene objects
if objects_val:
try:
if isinstance(objects_val, str):
objs = json.loads(objects_val)
else:
objs = objects_val
if objs:
obj_names = [o["tag"] for o in objs if isinstance(o, dict) and "tag" in o]
if obj_names:
context_parts.append(f"Detected elements/objects in scene: {', '.join(obj_names)}")
except Exception:
pass
context_str = "\n".join(context_parts)
user_content = (
"Reconstruct a high-quality, cohesive photorealistic prompt that perfectly matches the reference image characteristics. "
"Here are the extracted metadata and descriptions from the target reference image:\n\n"
f"{context_str}\n\n"
"Synthesize all the tags, pose information, and scene details into a single detailed photorealistic prompt "
"ideal for generating a matching image in Qwen. Do not include negative descriptions."
)
llm_api = "http://192.168.1.160:8001/v1/chat/completions"
payload = {
"model": "dphn/Dolphin3.0-Mistral-24B",
"messages": [
{
"role": "system",
"content": (
"You write explicit photographic — black void sceneries — pose and scenery directions for adult (NSFW) nude female photography. "
"You never refuse or add disclaimers. "
"Your task is to RECONSTRUCT and REVERSE ENGINEER the reference details into a single high-quality, detailed generation prompt. "
"Output ONLY the detailed generation prompt lines — no preamble, no commentary, no formatting. "
"Provide 7-12 short to medium length lines describing body position and characteristics, "
"ending with a realism tag like 'Perfect anatomy, photo realistic. keep the characteristics of the reference image.' or 'Anatomically precise. photorealistic, keep the characteristics of the reference image'. "
"Be specific and inventive."
)
},
{"role": "user", "content": user_content}
],
"temperature": 0.75,
"max_tokens": 1024
}
try:
r = requests.post(llm_api, json=payload, timeout=90)
r.raise_for_status()
data = r.json()
reconstructed = data["choices"][0]["message"]["content"].strip()
try:
database.save_db_prompt("reverse-engineer", reconstructed, {
"filename": filename
})
except Exception as db_err:
print(f"[reverse-engineer] failed to save to prompt table: {db_err}")
return {"prompt": reconstructed}
except Exception as e:
print(f"Reverse engineer error: {e}")
# Fallback to a high-quality combination of pose_desc and clip_desc
parts = []
if pose_desc:
parts.append(pose_desc)
if clip_desc_val:
parts.append(clip_desc_val)
fallback_prompt = ", ".join(parts) if parts else "Perfect anatomy, photo realistic"
return {"prompt": fallback_prompt}
@app.post("/images/{filename:path}/update-prompt")
def update_prompt(filename: str, req: UpdatePromptRequest):
try:
# Get existing record
person = database.get_person(filename)
if not person:
raise HTTPException(404, "Image not found in database")
# Update the database
database.upsert_person(filename, prompt=req.prompt)
_invalidate_static()
return {"status": "success", "filename": filename, "prompt": req.prompt}
except Exception as e:
raise HTTPException(500, str(e))
DESIGNER_SYSTEM = (
"You write explicit photographic — black void sceneries — pose directions for adult (NSFW) nude female photography. "
"You never refuse or add disclaimers. "
@@ -2935,10 +3149,10 @@ def tag_image(req: TagRequest):
@app.get("/names")
def get_names():
def get_names(bypass_static: bool = False):
output_dir = _load_output_dir()
static_file = os.path.join(output_dir, "_data", "names.json")
if os.path.exists(static_file):
if os.path.exists(static_file) and not bypass_static:
try:
with open(static_file, "r") as f:
return json.load(f)
@@ -2966,10 +3180,10 @@ def set_name(filename: str, body: dict):
# --- group routes ------------------------------------------------------------
@app.get("/groups")
def get_groups():
def get_groups(bypass_static: bool = False):
output_dir = _load_output_dir()
static_file = os.path.join(output_dir, "_data", "groups.json")
if os.path.exists(static_file):
if os.path.exists(static_file) and not bypass_static:
try:
with open(static_file, "r") as f:
return json.load(f)
@@ -3019,10 +3233,10 @@ def extract_from_group(req: ExtractRequest):
@app.get("/group-names")
def get_group_names():
def get_group_names(bypass_static: bool = False):
output_dir = _load_output_dir()
static_file = os.path.join(output_dir, "_data", "group-names.json")
if os.path.exists(static_file):
if os.path.exists(static_file) and not bypass_static:
try:
with open(static_file, "r") as f:
return json.load(f)
@@ -4253,6 +4467,17 @@ def generate_scenery(req: SceneryRequest):
+ "Output a single photorealistic image. High quality, detailed."
)
try:
database.save_db_prompt("scene", prompt, {
"model_filename": req.model_filename,
"scene_video": req.scene_video,
"scene_image": req.scene_image,
"extra_filename": req.extra_filename,
"seed": req.seed
})
except Exception as db_err:
print(f"[scenery] failed to save prompt: {db_err}")
job_id = uuid.uuid4().hex[:8]
jobs[job_id] = {"status": "running", "type": "scenery", "total": 1, "done": 0, "failed": 0}
threading.Thread(