save code

This commit is contained in:
Your Name
2026-03-12 10:14:01 +00:00
parent 42f83d3619
commit 8221c8be65
3 changed files with 78 additions and 24 deletions
+71
View File
@@ -0,0 +1,71 @@
# CLAUDE.md
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## Project Overview
This is a FastAPI-based virtual try-on service that integrates with ComfyUI for AI-powered clothing try-on. The service accepts base64-encoded images (model photo, shirt, pants) and returns a generated try-on result.
## Running the Service
Start the server:
```bash
python service2.py
```
The server runs on `http://0.0.0.0:47698` by default.
## Dependencies
Required Python packages (install via pip):
- fastapi
- uvicorn
- httpx
- pydantic
## Architecture
### Request Flow
1. Client sends POST to `/try-on` with base64 images (model_image, shirt_image, pants_image)
2. Server decodes images and uploads to ComfyUI at `http://127.0.0.1:8188`
3. Server modifies workflow JSON (change2_1_ex.json) with uploaded image filenames
4. Workflow submitted to ComfyUI queue
5. Server polls ComfyUI history endpoint until completion (max 300s timeout)
6. Result image downloaded from ComfyUI and returned as base64
### Key Components
**service2.py**: Main FastAPI application
- `/try-on`: Main endpoint for virtual try-on
- `/health`: Health check endpoint
- `/`: Serves static frontend (index.html)
**change2_1_ex.json**: ComfyUI workflow configuration
- Node "11": Model image input (LoadImage)
- Node "10": Shirt image input (LoadImage)
- Node "33": Output image (SaveImage)
- Node "31": Text prompt for image editing
**static/index.html**: Frontend interface for testing
### ComfyUI Integration
The service expects ComfyUI to be running at `http://127.0.0.1:8188` with:
- FireRed-Image-Edit model loaded
- Qwen 2.5 VL CLIP model
- Required custom nodes for QwenEditConfigPreparer, QwenEditOutputExtractor
### Workflow Modification
When processing requests, the code dynamically updates these workflow nodes:
- `workflow["11"]["inputs"]["image"]`: Model photo filename
- `workflow["10"]["inputs"]["image"]`: Shirt photo filename
- `workflow["31"]["inputs"]["value"]`: Text prompt (currently hardcoded in Chinese)
## Important Notes
- Images are uploaded with unique filenames (UUID prefix) to avoid caching issues
- The pants_image parameter is currently unused (commented out in code)
- Workflow uses 8 sampling steps with euler sampler and beta scheduler
- Output resolution is 768x1024 pixels
- CORS is enabled for all origins
+3 -9
View File
@@ -44,7 +44,6 @@ app.add_middleware(
class TryOnRequest(BaseModel): class TryOnRequest(BaseModel):
model_image: str # base64 模特图片 model_image: str # base64 模特图片
shirt_image: str # base64 上衣图片 shirt_image: str # base64 上衣图片
pants_image: str # base64 裤子图片
class TryOnResponse(BaseModel): class TryOnResponse(BaseModel):
@@ -113,7 +112,6 @@ async def try_on(req: TryOnRequest):
换装接口 换装接口
- model_image: 模特图片 base64 - model_image: 模特图片 base64
- shirt_image: 上衣图片 base64 - shirt_image: 上衣图片 base64
- pants_image: 裤子图片 base64
返回 result_image: 换装结果 base64 返回 result_image: 换装结果 base64
""" """
@@ -124,11 +122,10 @@ async def try_on(req: TryOnRequest):
async with httpx.AsyncClient(timeout=60.0) as client: async with httpx.AsyncClient(timeout=60.0) as client:
# 解码张图片 # 解码张图片
try: try:
model_bytes = decode_base64_image(req.model_image) model_bytes = decode_base64_image(req.model_image)
shirt_bytes = decode_base64_image(req.shirt_image) shirt_bytes = decode_base64_image(req.shirt_image)
# pants_bytes = decode_base64_image(req.pants_image)
except Exception as e: except Exception as e:
raise HTTPException(status_code=400, detail=f"图片解码失败: {e}") raise HTTPException(status_code=400, detail=f"图片解码失败: {e}")
@@ -136,14 +133,12 @@ async def try_on(req: TryOnRequest):
uid = uuid.uuid4().hex[:8] uid = uuid.uuid4().hex[:8]
model_fname = f"model_{uid}.jpg" model_fname = f"model_{uid}.jpg"
shirt_fname = f"shirt_{uid}.jpg" shirt_fname = f"shirt_{uid}.jpg"
# pants_fname = f"pants_{uid}.jpg"
# 并发上传张图片 # 并发上传张图片
try: try:
model_name, shirt_name, pants_name = await asyncio.gather( model_name, shirt_name = await asyncio.gather(
upload_image(client, model_bytes, model_fname), upload_image(client, model_bytes, model_fname),
upload_image(client, shirt_bytes, shirt_fname), upload_image(client, shirt_bytes, shirt_fname),
# upload_image(client, pants_bytes, pants_fname),
) )
except httpx.HTTPError as e: except httpx.HTTPError as e:
raise HTTPException(status_code=502, detail=f"上传图片到 ComfyUI 失败: {e}") raise HTTPException(status_code=502, detail=f"上传图片到 ComfyUI 失败: {e}")
@@ -151,7 +146,6 @@ async def try_on(req: TryOnRequest):
# 修改工作流节点的图片输入 # 修改工作流节点的图片输入
workflow[NODE_MODEL]["inputs"]["image"] = model_name workflow[NODE_MODEL]["inputs"]["image"] = model_name
workflow[NODE_SHIRT]["inputs"]["image"] = shirt_name workflow[NODE_SHIRT]["inputs"]["image"] = shirt_name
# workflow[NODE_PANTS]["inputs"]["image"] = pants_name
# 提交工作流 # 提交工作流
try: try:
+4 -15
View File
@@ -37,7 +37,7 @@
.upload-grid { .upload-grid {
display: grid; display: grid;
grid-template-columns: repeat(3, 1fr); grid-template-columns: repeat(2, 1fr);
gap: 20px; gap: 20px;
width: 100%; width: 100%;
max-width: 900px; max-width: 900px;
@@ -204,7 +204,7 @@
</head> </head>
<body> <body>
<h1>AI 换装系统</h1> <h1>AI 换装系统</h1>
<p class="subtitle">上传模特上衣、裤子三张图片,AI 自动完成换装</p> <p class="subtitle">上传模特上衣张图片,AI 自动完成换装</p>
<div class="upload-grid"> <div class="upload-grid">
<!-- 模特 --> <!-- 模特 -->
@@ -224,15 +224,6 @@
<div class="upload-hint">节点 10 · 上身服装</div> <div class="upload-hint">节点 10 · 上身服装</div>
<img class="preview-img" id="preview-shirt" alt="上衣预览" /> <img class="preview-img" id="preview-shirt" alt="上衣预览" />
</div> </div>
<!-- 裤子 -->
<div class="upload-card" id="card-pants">
<input type="file" accept="image/*" id="input-pants" />
<div class="upload-icon">👖</div>
<div class="upload-label">裤子图片</div>
<div class="upload-hint">节点 4 · 下身服装</div>
<img class="preview-img" id="preview-pants" alt="裤子预览" />
</div>
</div> </div>
<button class="btn-run" id="btn-run" disabled>开始换装</button> <button class="btn-run" id="btn-run" disabled>开始换装</button>
@@ -245,7 +236,7 @@
</div> </div>
<script> <script>
const images = { model: null, shirt: null, pants: null }; const images = { model: null, shirt: null };
function setupUpload(inputId, previewId, cardId, key) { function setupUpload(inputId, previewId, cardId, key) {
const input = document.getElementById(inputId); const input = document.getElementById(inputId);
@@ -269,11 +260,10 @@
setupUpload('input-model', 'preview-model', 'card-model', 'model'); setupUpload('input-model', 'preview-model', 'card-model', 'model');
setupUpload('input-shirt', 'preview-shirt', 'card-shirt', 'shirt'); setupUpload('input-shirt', 'preview-shirt', 'card-shirt', 'shirt');
setupUpload('input-pants', 'preview-pants', 'card-pants', 'pants');
function updateRunButton() { function updateRunButton() {
const btn = document.getElementById('btn-run'); const btn = document.getElementById('btn-run');
btn.disabled = !(images.model && images.shirt && images.pants); btn.disabled = !(images.model && images.shirt);
} }
function setStatus(msg, cls = '') { function setStatus(msg, cls = '') {
@@ -308,7 +298,6 @@
body: JSON.stringify({ body: JSON.stringify({
model_image: images.model, model_image: images.model,
shirt_image: images.shirt, shirt_image: images.shirt,
// pants_image: images.pants,
}), }),
}); });