auto-save 2026-05-17 16:27 (~4)
This commit is contained in:
41
api/main.py
41
api/main.py
@@ -4196,6 +4196,12 @@ class CopyCharacterLibraryAssetReq(BaseModel):
|
||||
character_id: str
|
||||
|
||||
|
||||
class GenerateProductAngleAssetReq(BaseModel):
|
||||
source_ref: dict
|
||||
target_view: str
|
||||
note: str = ""
|
||||
|
||||
|
||||
@app.get("/product-library/skg", response_model=list[ProductLibraryItem])
|
||||
def list_skg_product_library() -> list[ProductLibraryItem]:
|
||||
"""内置 SKG 白底产品图库。来源是本地筛选后的产品图 manifest。"""
|
||||
@@ -4254,6 +4260,41 @@ async def upload_storyboard_asset(job_id: str, file: UploadFile = File(...)) ->
|
||||
}
|
||||
|
||||
|
||||
@app.post("/jobs/{job_id}/assets/product-angle")
|
||||
def generate_product_angle_asset(job_id: str, req: GenerateProductAngleAssetReq) -> dict:
|
||||
if job_id not in JOBS:
|
||||
raise HTTPException(404, "job not found")
|
||||
source_path = storyboard_ref_path(job_id, req.source_ref)
|
||||
if not source_path or not source_path.exists():
|
||||
raise HTTPException(404, "source product image not found")
|
||||
target_view = (req.target_view or "目标视角").strip()
|
||||
note = (req.note or "").strip()
|
||||
prompt = (
|
||||
"Use the reference image as the same SKG neck-and-shoulder wearable massage product. "
|
||||
f"Generate a clean product-only white-background reference image in this missing view: {target_view}. "
|
||||
"Preserve the exact product identity: white U-shaped shoulder/neck device, asymmetric left and right details, side buttons, inner metal massage contacts, opening width, material, thickness, curvature, and scale. "
|
||||
"Do not mirror both sides into identical shapes; keep visible left/right asymmetry and believable shoulder-neck wearable proportions. "
|
||||
"The product should be complete, centered, isolated on pure white, large enough to inspect, with no hands, people, packaging, text, UI, watermark, extra accessories, or scene background. "
|
||||
"If the target view is not fully visible in the source, infer the missing surfaces conservatively from the same product design without inventing a new model. "
|
||||
+ (f"Additional operator note: {note}. " if note else "")
|
||||
)
|
||||
models = [IMAGE_MODEL, "gemini-3.1-flash-image-preview", "gemini-2.5-flash-image"]
|
||||
try:
|
||||
img_bytes, _mode = _image_edit_call(source_path, prompt, models=models, fallback_text=False, max_attempts=3, max_side=1280)
|
||||
except RuntimeError as e:
|
||||
raise HTTPException(500, f"product angle generation failed: {e}")
|
||||
asset_id = f"product_angle_{uuid.uuid4().hex[:10]}"
|
||||
out_path = job_dir(job_id) / "assets" / f"{asset_id}.jpg"
|
||||
_normalize_asset_image(img_bytes, out_path, source_path, "1024", "white", square=True, fill_subject=True)
|
||||
return {
|
||||
"kind": "asset",
|
||||
"frame_idx": -1,
|
||||
"element_id": asset_id,
|
||||
"cutout_id": asset_id,
|
||||
"label": f"AI 补角度 · {target_view}",
|
||||
}
|
||||
|
||||
|
||||
@app.post("/jobs/{job_id}/assets/product-library")
|
||||
def copy_product_library_asset(job_id: str, req: CopyProductLibraryAssetReq) -> dict:
|
||||
if job_id not in JOBS:
|
||||
|
||||
Reference in New Issue
Block a user