|
|
|
|
|
""" |
|
|
VINCIE Service UI (Gradio) |
|
|
- Automatic setup runs on app load (no manual setup button). |
|
|
- Multi-turn editing and multi-concept composition front-end. |
|
|
- Designed for NVIDIA L40S (SM 8.9) environments aligned with CUDA 12.x. |
|
|
- Functional reference: ByteDance-Seed/VINCIE. |
|
|
- Space and Docker developed by Carlex (contact below). |
|
|
""" |
|
|
|
|
|
import os |
|
|
from pathlib import Path |
|
|
from typing import List, Tuple, Optional |
|
|
|
|
|
import gradio as gr |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
from services.vincie import VincieService |
|
|
|
|
|
|
|
|
svc = VincieService() |
|
|
|
|
|
|
|
|
def setup_auto() -> str: |
|
|
""" |
|
|
Run an idempotent setup on interface load: |
|
|
- Ensure the upstream repository is present and ready. |
|
|
- Ensure the model checkpoint is downloaded and ready. |
|
|
Returns an English status string for the UI. |
|
|
""" |
|
|
try: |
|
|
svc.ensure_repo() |
|
|
svc.ensure_model() |
|
|
return ( |
|
|
"Setup completed successfully: repository and checkpoint are ready " |
|
|
"for inference on an NVIDIA L40S environment." |
|
|
) |
|
|
except Exception as e: |
|
|
return f"Setup encountered an error: {e}" |
|
|
|
|
|
|
|
|
def _list_media(out_dir: Path, max_images: int = 24) -> Tuple[List[str], Optional[str]]: |
|
|
""" |
|
|
Enumerate resulting images and the most recent video from an output directory. |
|
|
|
|
|
Args: |
|
|
out_dir: Path to the directory where the service wrote its results. |
|
|
max_images: Upper bound on how many images to surface in the gallery. |
|
|
|
|
|
Returns: |
|
|
A tuple (images, video) where: |
|
|
- images is a list of file paths to images sorted by modified time, |
|
|
- video is the path to the latest .mp4 if found, otherwise None. |
|
|
""" |
|
|
img_globs = ("*.png", "*.jpg", "*.jpeg", "*.webp") |
|
|
images: List[Path] = [] |
|
|
for pat in img_globs: |
|
|
images += list(out_dir.rglob(pat)) |
|
|
images = sorted(images, key=lambda p: p.stat().st_mtime) |
|
|
image_paths = [str(p) for p in images[-max_images:]] if images else [] |
|
|
videos = sorted(out_dir.rglob("*.mp4"), key=lambda p: p.stat().st_mtime) |
|
|
video_path = str(videos[-1]) if videos else None |
|
|
return image_paths, video_path |
|
|
|
|
|
|
|
|
def ui_multi_turn(input_image: Optional[str], turns_text: Optional[str]): |
|
|
""" |
|
|
Multi-turn image editing entrypoint for the UI. |
|
|
|
|
|
Args: |
|
|
input_image: Path to a single input image on disk. |
|
|
turns_text: User-provided editing turns, one instruction per line. |
|
|
|
|
|
Returns: |
|
|
(gallery, video, status) for Gradio components. |
|
|
""" |
|
|
if not input_image or not str(input_image).strip(): |
|
|
return [], None, "Please provide an input image." |
|
|
if not turns_text or not turns_text.strip(): |
|
|
return [], None, "Please provide edit turns (one per line)." |
|
|
|
|
|
turns = [ln.strip() for ln in turns_text.splitlines() if ln.strip()] |
|
|
try: |
|
|
out_dir = svc.multi_turn_edit(input_image, turns) |
|
|
except Exception as e: |
|
|
return [], None, f"Generation error: {e}" |
|
|
|
|
|
imgs, vid = _list_media(Path(out_dir)) |
|
|
status = f"Outputs saved to: {out_dir}" |
|
|
return imgs, vid, status |
|
|
|
|
|
|
|
|
def ui_multi_concept(files: Optional[List[str]], descs_text: Optional[str], final_prompt: Optional[str]): |
|
|
""" |
|
|
Multi-concept composition entrypoint for the UI. |
|
|
|
|
|
Args: |
|
|
files: List of paths to concept images on disk. |
|
|
descs_text: Per-image descriptions (one line per image, in the same order). |
|
|
final_prompt: A final composition prompt that aggregates the concepts. |
|
|
|
|
|
Returns: |
|
|
(gallery, video, status) for Gradio components. |
|
|
""" |
|
|
if not files: |
|
|
return [], None, "Please upload concept images." |
|
|
if not descs_text or not descs_text.strip(): |
|
|
return [], None, "Please provide descriptions (one per line)." |
|
|
if not final_prompt or not final_prompt.strip(): |
|
|
return [], None, "Please provide a final prompt." |
|
|
|
|
|
descs = [ln.strip() for ln in descs_text.splitlines() if ln.strip()] |
|
|
if len(descs) != len(files): |
|
|
return [], None, f"Descriptions count ({len(descs)}) must match images count ({len(files)})." |
|
|
|
|
|
try: |
|
|
out_dir = svc.multi_concept_compose(files, descs, final_prompt) |
|
|
except Exception as e: |
|
|
return [], None, f"Generation error: {e}" |
|
|
|
|
|
imgs, vid = _list_media(Path(out_dir)) |
|
|
status = f"Outputs saved to: {out_dir}" |
|
|
return imgs, vid, status |
|
|
|
|
|
|
|
|
with gr.Blocks(title="VINCIE Service") as demo: |
|
|
|
|
|
gr.Markdown( |
|
|
"\n".join( |
|
|
[ |
|
|
"# VINCIE Service — Multi-turn Editing and Multi-concept Composition", |
|
|
"- Automatic setup runs at startup; setup status appears below.", |
|
|
"- Hardware requirement: NVIDIA L40S (SM 8.9) is recommended for this build.", |
|
|
"- Functional upstream model: ByteDance-Seed/VINCIE (see project repository).", |
|
|
"- Space and Docker were developed by Carlex.", |
|
|
"- Contact: Email: Carlex22@gmail.com | GitHub: carlex22", |
|
|
] |
|
|
) |
|
|
) |
|
|
|
|
|
with gr.Row(): |
|
|
setup_out = gr.Textbox(label="Setup Status", interactive=False) |
|
|
|
|
|
with gr.Tab("Multi-turn Editing"): |
|
|
with gr.Row(): |
|
|
img = gr.Image(type="filepath", label="Input image") |
|
|
turns = gr.Textbox(lines=8, label="Turns (one per line)") |
|
|
run1 = gr.Button("Run") |
|
|
out_gallery = gr.Gallery(label="Images", columns=4, height="auto") |
|
|
out_video = gr.Video(label="Video (if available)") |
|
|
out_status = gr.Textbox(label="Output", interactive=False) |
|
|
run1.click(ui_multi_turn, inputs=[img, turns], outputs=[out_gallery, out_video, out_status]) |
|
|
|
|
|
with gr.Tab("Multi-concept Composition"): |
|
|
files = gr.File(file_count="multiple", type="filepath", label="Concept images") |
|
|
descs = gr.Textbox(lines=8, label="Descriptions (one per line, same order as images)") |
|
|
final_prompt = gr.Textbox(lines=2, label="Final prompt") |
|
|
run2 = gr.Button("Run") |
|
|
out_gallery2 = gr.Gallery(label="Images", columns=4, height="auto") |
|
|
out_video2 = gr.Video(label="Video (if available)") |
|
|
out_status2 = gr.Textbox(label="Output", interactive=False) |
|
|
run2.click( |
|
|
ui_multi_concept, |
|
|
inputs=[files, descs, final_prompt], |
|
|
outputs=[out_gallery2, out_video2, out_status2], |
|
|
) |
|
|
|
|
|
|
|
|
demo.load(fn=setup_auto, outputs=setup_out) |
|
|
|
|
|
if __name__ == "__main__": |
|
|
demo.launch( |
|
|
server_name="0.0.0.0", |
|
|
server_port=int(os.getenv("PORT", "7860")), |
|
|
allowed_paths=["/app/outputs", "/app/ckpt"], |
|
|
) |
|
|
|