updates UI
This commit is contained in:
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user