Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
@@ -15,36 +15,94 @@ if os.path.exists('.env'):
|
|
15 |
else:
|
16 |
print("Warning: .env file not found. Using default environment variables.")
|
17 |
|
|
|
18 |
api_key_token = os.getenv('api_key_token', '')
|
19 |
groq_api_key = os.getenv('groq_api_key', '')
|
20 |
|
21 |
if not api_key_token or not groq_api_key:
|
22 |
raise ValueError("Please configure your API keys in .env file")
|
23 |
|
24 |
-
|
25 |
print("Version 2.19 - Fixed RGBA to RGB conversion for JPEG")
|
26 |
print("Running grok_app.py with workflow processing and .env configuration")
|
27 |
|
|
|
28 |
url_pre = "https://ap-east-1.tensorart.cloud/v1"
|
29 |
SAVE_DIR = "generated_images"
|
30 |
Path(SAVE_DIR).mkdir(exist_ok=True)
|
31 |
|
|
|
32 |
PRODUCT_GROUPS = {
|
33 |
-
"Standard": {
|
34 |
-
|
35 |
-
|
36 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
37 |
}
|
38 |
|
|
|
39 |
PRODUCT_IMAGE_MAP = {
|
40 |
-
"C1012 Glacier White": "product_images/C1012.jpg",
|
41 |
-
"
|
42 |
-
"
|
43 |
-
"
|
44 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
45 |
"C4255 Calacatta Extra": "product_images/C4255.jpg",
|
46 |
}
|
47 |
|
|
|
48 |
GROUP_COLORS = {
|
49 |
"Standard": "#FFCCCC",
|
50 |
"Deluxe": "#CCFFCC",
|
@@ -52,39 +110,24 @@ GROUP_COLORS = {
|
|
52 |
"Super Luxury": "#CCFCFF",
|
53 |
}
|
54 |
|
|
|
55 |
client = Groq(api_key=groq_api_key)
|
56 |
|
57 |
def rewrite_prompt_with_groq(vietnamese_prompt, product_codes):
|
58 |
-
|
59 |
-
|
60 |
-
"You are an expert in interior design descriptions. Rewrite the user's prompt "
|
61 |
-
"to include the provided product codes in a natural and appealing way, ensuring "
|
62 |
-
"the description remains coherent and attractive. Return only the rewritten prompt without anything else."
|
63 |
-
)
|
64 |
-
user_message = f"Prompt: {vietnamese_prompt}"
|
65 |
-
|
66 |
-
print("Sending request to Groq API for prompt rewriting...")
|
67 |
-
response = client.chat.completions.create(
|
68 |
-
model="mixtral-8x7b-32768",
|
69 |
-
messages=[
|
70 |
-
{"role": "system", "content": system_prompt},
|
71 |
-
{"role": "user", "content": user_message}
|
72 |
-
],
|
73 |
-
max_tokens=200,
|
74 |
-
temperature=0.7
|
75 |
-
)
|
76 |
-
|
77 |
-
rewritten_prompt = response.choices[0].message.content.strip()
|
78 |
-
print(f"Rewritten prompt from Groq: {rewritten_prompt}")
|
79 |
-
return rewritten_prompt
|
80 |
|
|
|
81 |
def upload_image_to_tensorart(image_path):
|
82 |
-
print(f"Starting image upload process for: {image_path}")
|
83 |
try:
|
84 |
url = f"{url_pre}/resource/image"
|
85 |
payload = json.dumps({"expireSec": "7200"})
|
86 |
-
headers = {
|
87 |
-
|
|
|
|
|
|
|
|
|
88 |
if not os.path.exists(image_path):
|
89 |
print(f"File does not exist: {image_path}")
|
90 |
return None
|
@@ -103,77 +146,81 @@ def upload_image_to_tensorart(image_path):
|
|
103 |
with open(image_path, 'rb') as img_file:
|
104 |
upload_response = requests.put(put_url, data=img_file, headers=headers_put)
|
105 |
print(f"PUT response: {upload_response.status_code} - {upload_response.text}")
|
106 |
-
if upload_response.status_code
|
107 |
-
print("Upload completed successfully.")
|
108 |
-
elif upload_response.status_code == 203:
|
109 |
-
print("Warning: Upload completed with 'CallbackFailed'. Proceeding with resourceId, but callback to TensorArt failed.")
|
110 |
-
else:
|
111 |
raise Exception(f"PUT failed with status {upload_response.status_code}: {upload_response.text}")
|
|
|
|
|
112 |
|
113 |
resource_id = resource_response.get('resourceId')
|
114 |
if not resource_id:
|
115 |
print(f"Upload failed - No 'resourceId' in response: {resource_response}")
|
116 |
return None
|
117 |
print(f"Upload successful - resourceId: {resource_id}")
|
118 |
-
time.sleep(10)
|
|
|
119 |
return resource_id
|
120 |
except Exception as e:
|
121 |
print(f"Upload error for {image_path}: {str(e)}")
|
122 |
return None
|
123 |
|
|
|
124 |
def check_workflow_params(params):
|
125 |
-
print("Checking workflow parameters...")
|
126 |
url = f"{url_pre}/jobs/workflow/params/check"
|
127 |
-
headers = {
|
|
|
|
|
|
|
|
|
128 |
payload = json.dumps({"params": params})
|
129 |
-
print(f"
|
130 |
response = requests.post(url, headers=headers, data=payload, timeout=30)
|
131 |
print(f"Params check response: {response.status_code} - {response.text}")
|
132 |
if response.status_code != 200:
|
133 |
raise Exception(f"Params check failed: {response.text}")
|
134 |
return response.json()
|
135 |
|
|
|
136 |
def run_workflow(payload, step_name):
|
137 |
-
print(f"Running workflow for step: {step_name}")
|
138 |
-
print(f"Payload: {json.dumps(payload, indent=2)}")
|
139 |
try:
|
140 |
-
|
141 |
-
print(f"Params check result: {check_result}")
|
142 |
except Exception as e:
|
143 |
print(f"Workflow params check failed for {step_name}: {str(e)}")
|
144 |
raise
|
145 |
-
|
146 |
-
|
|
|
|
|
|
|
|
|
|
|
147 |
response = requests.post(f"{url_pre}/jobs/workflow", json=payload, headers=headers, timeout=300)
|
148 |
-
print(f"
|
149 |
if response.status_code != 200:
|
150 |
raise Exception(f"Error {response.status_code}: {response.text}")
|
|
|
151 |
response_data = response.json()
|
152 |
job_id = response_data['job'].get('id')
|
153 |
if not job_id:
|
154 |
raise Exception("Không tìm thấy job_id trong response")
|
155 |
-
print(f"Job submitted successfully, job_id: {job_id}")
|
156 |
|
|
|
157 |
max_attempts = 36
|
158 |
for attempt in range(max_attempts):
|
159 |
-
print(f"Checking job status, attempt {attempt + 1}/{max_attempts}")
|
160 |
response = requests.get(f"{url_pre}/jobs/{job_id}", headers=headers, timeout=30)
|
161 |
-
print(f"Job status response: {response.status_code} - {response.text}")
|
162 |
response.raise_for_status()
|
163 |
result = response.json()
|
|
|
164 |
status = result['job'].get('status')
|
165 |
-
print(f"Current job status: {status}")
|
166 |
if status == 'SUCCESS':
|
167 |
success_info = result['job'].get('successInfo', {})
|
168 |
if not success_info.get('images'):
|
169 |
raise Exception(f"Không tìm thấy hình ảnh trong successInfo cho {step_name}")
|
170 |
image_url = success_info['images'][0]['url']
|
171 |
-
print(f"Downloading image from: {image_url}")
|
172 |
image_response = requests.get(image_url)
|
173 |
image = Image.open(BytesIO(image_response.content))
|
|
|
174 |
if image.mode == 'RGBA':
|
175 |
image = image.convert('RGB')
|
176 |
-
print("Converted image from RGBA to RGB")
|
177 |
output_path = Path(SAVE_DIR) / f"{step_name}_{int(time.time())}.jpg"
|
178 |
image.save(output_path)
|
179 |
print(f"{step_name} image saved to: {output_path}")
|
@@ -181,394 +228,314 @@ def run_workflow(payload, step_name):
|
|
181 |
elif status in ['FAILED', 'ERROR']:
|
182 |
failed_info = result['job'].get('failedInfo', {})
|
183 |
error_reason = failed_info.get('reason', 'Không có chi tiết')
|
184 |
-
|
|
|
185 |
time.sleep(5)
|
186 |
raise Exception(f"Hết thời gian chờ {step_name} job sau 3 phút")
|
187 |
|
188 |
-
|
189 |
-
|
190 |
-
|
191 |
-
|
192 |
-
|
193 |
-
|
194 |
-
|
195 |
-
|
196 |
-
|
197 |
-
|
198 |
-
|
199 |
-
|
200 |
-
|
201 |
-
|
202 |
-
|
203 |
-
|
204 |
-
|
205 |
-
|
206 |
-
|
207 |
-
|
208 |
-
|
209 |
-
|
210 |
-
},
|
211 |
-
|
212 |
-
|
213 |
-
|
214 |
-
"
|
215 |
-
|
216 |
-
|
217 |
-
|
218 |
-
|
219 |
-
|
220 |
-
|
221 |
-
|
222 |
-
|
223 |
-
|
224 |
-
|
225 |
-
|
226 |
-
|
227 |
-
|
228 |
-
|
229 |
-
|
230 |
-
|
231 |
-
"31": {
|
232 |
-
"classType": "KSampler",
|
233 |
-
"inputs": {
|
234 |
-
"model": ["44", 0],
|
235 |
-
"positive": ["35", 0],
|
236 |
-
"negative": ["41", 0],
|
237 |
-
"latent_image": ["27", 0],
|
238 |
-
"seed": ["47", 0],
|
239 |
-
"steps": 20,
|
240 |
-
"cfg": 1,
|
241 |
-
"sampler_name": "euler",
|
242 |
-
"scheduler": "normal",
|
243 |
-
"denoise": 1
|
244 |
},
|
245 |
-
"
|
246 |
-
|
247 |
-
|
248 |
-
|
249 |
-
|
250 |
-
|
251 |
-
|
252 |
-
|
253 |
-
|
254 |
-
"inputs": {"ckpt_name": "779398605850080514"},
|
255 |
-
"properties": {"Node name for S&R": "TensorArt_CheckpointLoader"}
|
256 |
-
},
|
257 |
-
"56": {
|
258 |
-
"classType": "Lora Loader",
|
259 |
-
"inputs": {
|
260 |
-
"model": ["43", 0],
|
261 |
-
"clip": ["43", 1],
|
262 |
-
"lora_name": "792964023831298420",
|
263 |
-
"strength_model": 1,
|
264 |
-
"strength_clip": 1
|
265 |
},
|
266 |
-
"
|
267 |
-
|
268 |
-
|
269 |
-
|
270 |
-
|
271 |
-
|
272 |
-
|
273 |
-
|
274 |
-
"
|
275 |
-
"strength_clip": 1
|
276 |
},
|
277 |
-
"
|
278 |
-
|
279 |
-
|
280 |
-
|
281 |
-
|
282 |
-
|
283 |
-
|
284 |
-
|
285 |
-
"
|
286 |
-
"mode": "randomize",
|
287 |
-
"max_images": 1,
|
288 |
-
"max_tokens": 4093,
|
289 |
-
"censor": "disable"
|
290 |
},
|
291 |
-
"
|
292 |
-
|
293 |
-
|
294 |
-
|
295 |
-
|
296 |
-
|
297 |
-
|
298 |
-
|
299 |
-
|
300 |
-
"inputs": {"images": ["54", 0], "blending": 0.4, "tiled": "true", "tiles": 2},
|
301 |
-
"properties": {"Node name for S&R": "Image Seamless Texture"}
|
302 |
-
},
|
303 |
-
"50": {
|
304 |
-
"classType": "LayerMask: SegmentAnythingUltra V3",
|
305 |
-
"inputs": {
|
306 |
-
"image": ["8", 0],
|
307 |
-
"sam_models": ["51", 0],
|
308 |
-
"prompt": ["49", 0],
|
309 |
-
"threshold": 0.3,
|
310 |
-
"detail_method": "PyMatting",
|
311 |
-
"detail_erode": 16,
|
312 |
-
"detail_dilate": 16,
|
313 |
-
"black_point": 0.15,
|
314 |
-
"white_point": 0.99,
|
315 |
-
"process_detail": True,
|
316 |
-
"device": "cuda",
|
317 |
-
"max_megapixels": 2
|
318 |
},
|
319 |
-
"
|
320 |
-
|
321 |
-
|
322 |
-
|
323 |
-
|
324 |
-
|
325 |
-
|
326 |
-
"52": {
|
327 |
-
"classType": "MaskToImage",
|
328 |
-
"inputs": {"mask": ["50", 1]},
|
329 |
-
"properties": {"Node name for S&R": "MaskToImage"}
|
330 |
-
},
|
331 |
-
"55": {
|
332 |
-
"classType": "Paste By Mask",
|
333 |
-
"inputs": {"image_base": ["8", 0], "image_to_paste": ["53", 0], "mask": ["52", 0], "mask_mapping_optional": None, "resize_behavior": "resize"},
|
334 |
-
"properties": {"Node name for S&R": "Paste By Mask"}
|
335 |
-
},
|
336 |
-
"9": {
|
337 |
-
"classType": "SaveImage",
|
338 |
-
"inputs": {
|
339 |
-
"filename_prefix": "TensorArt",
|
340 |
-
"images": ["55", 0]
|
341 |
},
|
342 |
-
"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
343 |
}
|
344 |
-
}
|
345 |
-
|
346 |
-
payload = {
|
347 |
-
"requestId": f"workflow_{int(time.time())}",
|
348 |
-
"model_id": "779398605850080514",
|
349 |
-
"params": workflow_params,
|
350 |
-
"runningNotifyUrl": ""
|
351 |
-
}
|
352 |
-
|
353 |
-
print("Preparing to run Text2Img workflow")
|
354 |
-
output_path = run_workflow(payload, "text2img")
|
355 |
-
print(f"Text2Img workflow completed, output_path: {output_path}")
|
356 |
-
return output_path
|
357 |
|
358 |
-
|
359 |
-
|
360 |
-
|
361 |
-
|
362 |
-
|
363 |
-
|
364 |
-
|
365 |
-
|
366 |
-
yield "Vui lòng nhập kích thước tùy chỉnh.", gr.update(visible=False), gr.update(visible=False), None
|
367 |
-
return
|
368 |
-
width, height = map(int, custom_size.split("x"))
|
369 |
-
print(f"Parsed custom size: width={width}, height={height}")
|
370 |
-
else:
|
371 |
-
width, height = map(int, size_choice.split("x"))
|
372 |
-
print(f"Selected predefined size: width={width}, height={height}")
|
373 |
-
|
374 |
-
selected_products = []
|
375 |
-
for group, choices in zip(PRODUCT_GROUPS.keys(), product_choices):
|
376 |
-
selected_products.extend(choices)
|
377 |
-
if not selected_products:
|
378 |
-
print("Error: No product selected")
|
379 |
-
yield "Vui lòng chọn ít nhất một mã sản phẩm.", gr.update(visible=False), gr.update(visible=False), None
|
380 |
-
return
|
381 |
-
selected_product_code = selected_products[0]
|
382 |
-
print(f"Selected product code: {selected_product_code}")
|
383 |
-
|
384 |
-
rewritten_prompt = rewrite_prompt_with_groq(prompt, [selected_product_code.split()[0]])
|
385 |
-
output_path = generate_text2img_workflow(rewritten_prompt, width, height, selected_product_code)
|
386 |
-
if not output_path:
|
387 |
-
print("Error: Workflow returned no output")
|
388 |
-
yield "Lỗi: Không thể tạo ảnh", gr.update(visible=False), gr.update(visible=False), None
|
389 |
-
return
|
390 |
|
391 |
-
print("Text2Img generation completed successfully")
|
392 |
-
yield "Hoàn tất!", gr.update(visible=False), gr.update(visible=True, value='<div class="progress-container"><div class="progress-bar" style="width: 100%"></div></div>'), Image.open(output_path)
|
393 |
except Exception as e:
|
394 |
-
print(f"
|
395 |
-
|
396 |
-
|
397 |
-
def generate_img2img_workflow(image_resource_id, position, selected_product_code):
|
398 |
-
print(f"Starting Img2Img workflow with image_resource_id: {image_resource_id}, position: {position}, product_code: {selected_product_code}")
|
399 |
-
texture_filepath = PRODUCT_IMAGE_MAP.get(selected_product_code)
|
400 |
-
if not texture_filepath or not os.path.exists(texture_filepath):
|
401 |
-
print(f"Error: Texture file not found for product code {selected_product_code}")
|
402 |
-
raise Exception(f"Không tìm thấy ảnh sản phẩm cho mã {selected_product_code}")
|
403 |
-
texture_resource_id = upload_image_to_tensorart(texture_filepath)
|
404 |
-
if not texture_resource_id:
|
405 |
-
print(f"Error: Failed to upload texture for product code {selected_product_code}")
|
406 |
-
raise Exception(f"Không thể upload ảnh sản phẩm {selected_product_code}")
|
407 |
-
print(f"Texture uploaded successfully, resource_id: {texture_resource_id}")
|
408 |
-
|
409 |
-
workflow_params = {
|
410 |
-
"1": {
|
411 |
-
"classType": "LayerMask: SegmentAnythingUltra V3",
|
412 |
-
"inputs": {
|
413 |
-
"black_point": 0.15,
|
414 |
-
"detail_dilate": 38,
|
415 |
-
"detail_erode": 15,
|
416 |
-
"detail_method": "PyMatting",
|
417 |
-
"device": "cuda",
|
418 |
-
"image": ["2", 0],
|
419 |
-
"max_megapixels": 2,
|
420 |
-
"process_detail": True,
|
421 |
-
"prompt": ["4", 0],
|
422 |
-
"sam_models": ["3", 0],
|
423 |
-
"threshold": 0.3,
|
424 |
-
"white_point": 0.99
|
425 |
-
},
|
426 |
-
"properties": {"Node name for S&R": "LayerMask: SegmentAnythingUltra V3"}
|
427 |
-
},
|
428 |
-
"10": {
|
429 |
-
"classType": "Image Seamless Texture",
|
430 |
-
"inputs": {
|
431 |
-
"blending": 0.37,
|
432 |
-
"images": ["17", 0],
|
433 |
-
"tiled": "true",
|
434 |
-
"tiles": 2
|
435 |
-
},
|
436 |
-
"properties": {"Node name for S&R": "Image Seamless Texture"}
|
437 |
-
},
|
438 |
-
"13": {
|
439 |
-
"classType": "Paste By Mask",
|
440 |
-
"inputs": {
|
441 |
-
"image_base": ["2", 0],
|
442 |
-
"image_to_paste": ["10", 0],
|
443 |
-
"mask": ["8", 0],
|
444 |
-
"resize_behavior": "resize"
|
445 |
-
},
|
446 |
-
"properties": {"Node name for S&R": "Paste By Mask"}
|
447 |
-
},
|
448 |
-
"17": {
|
449 |
-
"classType": "TensorArt_LoadImage",
|
450 |
-
"inputs": {
|
451 |
-
"_height": 768,
|
452 |
-
"_width": 512,
|
453 |
-
"image": texture_resource_id,
|
454 |
-
"upload": "image"
|
455 |
-
},
|
456 |
-
"properties": {"Node name for S&R": "TensorArt_LoadImage"}
|
457 |
-
},
|
458 |
-
"2": {
|
459 |
-
"classType": "TensorArt_LoadImage",
|
460 |
-
"inputs": {
|
461 |
-
"_height": 1024,
|
462 |
-
"_width": 768,
|
463 |
-
"image": image_resource_id,
|
464 |
-
"upload": "image"
|
465 |
-
},
|
466 |
-
"properties": {"Node name for S&R": "TensorArt_LoadImage"}
|
467 |
-
},
|
468 |
-
"3": {
|
469 |
-
"classType": "LayerMask: LoadSegmentAnythingModels",
|
470 |
-
"inputs": {
|
471 |
-
"grounding_dino_model": "GroundingDINO_SwinB (938MB)",
|
472 |
-
"sam_model": "sam_vit_h (2.56GB)"
|
473 |
-
},
|
474 |
-
"properties": {"Node name for S&R": "LayerMask: LoadSegmentAnythingModels"}
|
475 |
-
},
|
476 |
-
"4": {
|
477 |
-
"classType": "TensorArt_PromptText",
|
478 |
-
"inputs": {"Text": position.lower()},
|
479 |
-
"properties": {"Node name for S&R": "TensorArt_PromptText"}
|
480 |
-
},
|
481 |
-
"7": {
|
482 |
-
"classType": "PreviewImage",
|
483 |
-
"inputs": {"images": ["13", 0]},
|
484 |
-
"properties": {"Node name for S&R": "PreviewImage"}
|
485 |
-
},
|
486 |
-
"8": {
|
487 |
-
"classType": "MaskToImage",
|
488 |
-
"inputs": {"mask": ["1", 1]},
|
489 |
-
"properties": {"Node name for S&R": "MaskToImage"}
|
490 |
-
}
|
491 |
-
}
|
492 |
|
493 |
-
|
494 |
-
|
495 |
-
|
496 |
-
|
497 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
498 |
|
499 |
-
|
500 |
-
output_path = run_workflow(payload, "img2img")
|
501 |
-
print(f"Img2Img workflow completed, output_path: {output_path}")
|
502 |
-
return output_path
|
503 |
|
504 |
-
|
505 |
-
|
506 |
yield None, gr.update(visible=True), gr.update(visible=True, value='<div class="progress-container"><div class="progress-bar" style="width: 0%"></div></div>'), None
|
507 |
try:
|
508 |
-
print(f"Processing size choice: {size_choice}, custom_size: {custom_size}")
|
509 |
if size_choice == "Custom size":
|
510 |
if not custom_size.strip():
|
511 |
-
print("Error: Custom size input is empty")
|
512 |
yield "Vui lòng nhập kích thước tùy chỉnh.", gr.update(visible=False), gr.update(visible=False), None
|
513 |
return
|
514 |
width, height = map(int, custom_size.split("x"))
|
515 |
-
print(f"Parsed custom size: width={width}, height={height}")
|
516 |
else:
|
517 |
width, height = map(int, size_choice.split("x"))
|
518 |
-
print(f"Selected predefined size: width={width}, height={height}")
|
519 |
-
|
520 |
-
image_path = Path(SAVE_DIR) / f"input_{int(time.time())}.jpg"
|
521 |
-
print(f"Saving input image to: {image_path}")
|
522 |
-
image.save(image_path)
|
523 |
-
image_resource_id = upload_image_to_tensorart(str(image_path))
|
524 |
-
if not image_resource_id:
|
525 |
-
print("Error: Failed to upload input image")
|
526 |
-
yield "Lỗi: Không thể upload ảnh gốc", gr.update(visible=False), gr.update(visible=False), None
|
527 |
-
return
|
528 |
-
print(f"Input image uploaded successfully, resource_id: {image_resource_id}")
|
529 |
|
530 |
selected_products = []
|
531 |
for group, choices in zip(PRODUCT_GROUPS.keys(), product_choices):
|
532 |
selected_products.extend(choices)
|
533 |
if not selected_products:
|
534 |
-
print("Error: No product selected")
|
535 |
yield "Vui lòng chọn ít nhất một mã sản phẩm.", gr.update(visible=False), gr.update(visible=False), None
|
536 |
return
|
537 |
-
selected_product_code = selected_products[0]
|
538 |
-
print(f"Selected product code: {selected_product_code}")
|
539 |
|
540 |
-
|
541 |
-
|
542 |
-
|
543 |
-
|
544 |
-
|
545 |
-
|
546 |
-
|
547 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
548 |
except Exception as e:
|
549 |
-
print(f"Img2Img generation failed: {str(e)}")
|
550 |
yield f"Lỗi khi xử lý: {e}", gr.update(visible=False), gr.update(visible=False), None
|
551 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
552 |
css = """
|
553 |
.loading-spinner { border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: auto; }
|
554 |
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
555 |
.progress-container { width: 100%; max-width: 400px; margin: 20px auto; background-color: #f3f3f3; border-radius: 20px; overflow: hidden; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); }
|
556 |
.progress-bar { width: 0%; height: 10px; background: linear-gradient(90deg, #3498db, #e74c3c); border-radius: 20px; transition: width 0.3s ease-in-out; }
|
557 |
-
|
558 |
-
.image-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
559 |
"""
|
560 |
|
|
|
561 |
with gr.Blocks(css=css) as demo:
|
562 |
-
gr.Markdown("## CaslaQuartz
|
563 |
with gr.Tabs():
|
564 |
-
with gr.Tab("
|
565 |
with gr.Row():
|
566 |
with gr.Column():
|
567 |
-
|
568 |
-
|
569 |
-
|
570 |
-
|
571 |
-
|
572 |
for group, color in GROUP_COLORS.items():
|
573 |
with gr.Accordion(f"Sản phẩm - {group}", open=False):
|
574 |
checkboxes = gr.CheckboxGroup(
|
@@ -576,42 +543,88 @@ with gr.Blocks(css=css) as demo:
|
|
576 |
label=f"Chọn sản phẩm ({group})",
|
577 |
value=[]
|
578 |
)
|
579 |
-
|
580 |
-
|
581 |
with gr.Column():
|
582 |
-
|
583 |
-
|
584 |
-
|
585 |
-
|
586 |
-
|
587 |
-
|
588 |
-
|
589 |
-
with gr.Tab("
|
590 |
-
with gr.Row():
|
591 |
-
img2img_image_upload = gr.Image(label="Ảnh không gian muốn ứng dụng", type="pil", height=400)
|
592 |
-
img2img_output_image = gr.Image(label="Ảnh kết quả", height=400)
|
593 |
-
with gr.Row():
|
594 |
-
img2img_position_input = gr.Dropdown(label="Vị trí áp dụng", choices=["Wall", "Countertop", "Floor", "Counter", "Table", "Coffee Table top", "Backsplash"], value="Countertop")
|
595 |
with gr.Row():
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
596 |
with gr.Column():
|
597 |
-
|
598 |
-
|
599 |
-
|
600 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
601 |
for group, color in GROUP_COLORS.items():
|
602 |
with gr.Accordion(f"Sản phẩm - {group}", open=False):
|
603 |
checkboxes = gr.CheckboxGroup(
|
604 |
-
choices=[(code, code) for code in PRODUCT_GROUPS[group]
|
|
|
605 |
label=f"Chọn sản phẩm ({group})",
|
606 |
-
value=[]
|
|
|
607 |
)
|
608 |
-
|
609 |
-
|
610 |
-
|
611 |
-
|
612 |
-
|
613 |
-
|
614 |
-
|
615 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
616 |
|
617 |
demo.launch(share=True)
|
|
|
15 |
else:
|
16 |
print("Warning: .env file not found. Using default environment variables.")
|
17 |
|
18 |
+
# Get environment variables with fallback
|
19 |
api_key_token = os.getenv('api_key_token', '')
|
20 |
groq_api_key = os.getenv('groq_api_key', '')
|
21 |
|
22 |
if not api_key_token or not groq_api_key:
|
23 |
raise ValueError("Please configure your API keys in .env file")
|
24 |
|
25 |
+
# Ghi log khởi động
|
26 |
print("Version 2.19 - Fixed RGBA to RGB conversion for JPEG")
|
27 |
print("Running grok_app.py with workflow processing and .env configuration")
|
28 |
|
29 |
+
# URL và thông tin cá nhân từ API
|
30 |
url_pre = "https://ap-east-1.tensorart.cloud/v1"
|
31 |
SAVE_DIR = "generated_images"
|
32 |
Path(SAVE_DIR).mkdir(exist_ok=True)
|
33 |
|
34 |
+
# Danh sách mã sản phẩm
|
35 |
PRODUCT_GROUPS = {
|
36 |
+
"Standard": {
|
37 |
+
"C1012 Glacier White": "817687427545199895",
|
38 |
+
"C1026 Polar": "819910519797326073",
|
39 |
+
"C3269 Ash Grey": "821839484099264081",
|
40 |
+
"C3168 Silver Wave": "821849044696643212",
|
41 |
+
"C1005 Milky White": "821948258441171133",
|
42 |
+
},
|
43 |
+
"Deluxe": {
|
44 |
+
"C2103 Onyx Carrara": "827090618489513527",
|
45 |
+
"C2104 Massa": "822075428127644644",
|
46 |
+
"C3105 Casla Cloudy": "828912225788997963",
|
47 |
+
"C3146 Casla Nova": "828013009961087650",
|
48 |
+
"C2240 Marquin": "828085015087780649",
|
49 |
+
"C2262 Concrete (Honed)": "822211862058871636",
|
50 |
+
"C3311 Calacatta Sky": "829984593223502930",
|
51 |
+
"C3346 Massimo": "827938741386607132",
|
52 |
+
},
|
53 |
+
"Luxury": {
|
54 |
+
"C4143 Mario": "829984593223502930",
|
55 |
+
"C4145 Marina": "828132560375742058",
|
56 |
+
"C4202 Calacatta Gold": "828167757632695310",
|
57 |
+
"C1205 Casla Everest": "828296778450463190",
|
58 |
+
"C4211 Calacatta Supreme": "828436321937882328",
|
59 |
+
"C4204 Calacatta Classic": "828422973179466146",
|
60 |
+
"C5240 Spring": "is coming",
|
61 |
+
"C1102 Super White": "828545723344775887",
|
62 |
+
"C4246 Casla Mystery": "828544778451950698",
|
63 |
+
"C4345 Oro": "828891068780182635",
|
64 |
+
"C4346 Luxe": "829436426547535131",
|
65 |
+
"C4342 Casla Eternal": "829190256201829181",
|
66 |
+
"C4221 Athena": "829644354504131520",
|
67 |
+
"C4222 Lagoon": "is coming",
|
68 |
+
"C5225 Amber": "is coming",
|
69 |
+
},
|
70 |
+
"Super Luxury": {
|
71 |
+
"C4255 Calacatta Extra": "829659013227537217",
|
72 |
+
},
|
73 |
}
|
74 |
|
75 |
+
# Danh sách ảnh sản phẩm tương ứng với mã sản phẩm
|
76 |
PRODUCT_IMAGE_MAP = {
|
77 |
+
"C1012 Glacier White": "product_images/C1012.jpg",
|
78 |
+
"C1026 Polar": "product_images/C1026.jpg",
|
79 |
+
"C3269 Ash Grey": "product_images/C3269.jpg",
|
80 |
+
"C3168 Silver Wave": "product_images/C3168.jpg",
|
81 |
+
"C1005 Milky White": "product_images/C1005.jpg",
|
82 |
+
"C2103 Onyx Carrara": "product_images/C2103.jpg",
|
83 |
+
"C2104 Massa": "product_images/C2104.jpg",
|
84 |
+
"C3105 Casla Cloudy": "product_images/C3105.jpg",
|
85 |
+
"C3146 Casla Nova": "product_images/C3146.jpg",
|
86 |
+
"C2240 Marquin": "product_images/C2240.jpg",
|
87 |
+
"C2262 Concrete (Honed)": "product_images/C2262.jpg",
|
88 |
+
"C3311 Calacatta Sky": "product_images/C3311.jpg",
|
89 |
+
"C3346 Massimo": "product_images/C3346.jpg",
|
90 |
+
"C4143 Mario": "product_images/C4143.jpg",
|
91 |
+
"C4145 Marina": "product_images/C4145.jpg",
|
92 |
+
"C4202 Calacatta Gold": "product_images/C4202.jpg",
|
93 |
+
"C1205 Casla Everest": "product_images/C1205.jpg",
|
94 |
+
"C4211 Calacatta Supreme": "product_images/C4211.jpg",
|
95 |
+
"C4204 Calacatta Classic": "product_images/C4204.jpg",
|
96 |
+
"C1102 Super White": "product_images/C1102.jpg",
|
97 |
+
"C4246 Casla Mystery": "product_images/C4246.jpg",
|
98 |
+
"C4345 Oro": "product_images/C4345.jpg",
|
99 |
+
"C4346 Luxe": "product_images/C4346.jpg",
|
100 |
+
"C4342 Casla Eternal": "product_images/C4342.jpg",
|
101 |
+
"C4221 Athena": "product_images/C4221.jpg",
|
102 |
"C4255 Calacatta Extra": "product_images/C4255.jpg",
|
103 |
}
|
104 |
|
105 |
+
# Định nghĩa màu sắc cho từng nhóm sản phẩm
|
106 |
GROUP_COLORS = {
|
107 |
"Standard": "#FFCCCC",
|
108 |
"Deluxe": "#CCFFCC",
|
|
|
110 |
"Super Luxury": "#CCFCFF",
|
111 |
}
|
112 |
|
113 |
+
# Khởi tạo client Groq
|
114 |
client = Groq(api_key=groq_api_key)
|
115 |
|
116 |
def rewrite_prompt_with_groq(vietnamese_prompt, product_codes):
|
117 |
+
prompt = f"{vietnamese_prompt}, featuring {' and '.join(product_codes)} quartz marble"
|
118 |
+
return prompt
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
119 |
|
120 |
+
# Hàm upload ảnh lên TensorArt
|
121 |
def upload_image_to_tensorart(image_path):
|
|
|
122 |
try:
|
123 |
url = f"{url_pre}/resource/image"
|
124 |
payload = json.dumps({"expireSec": "7200"})
|
125 |
+
headers = {
|
126 |
+
'Content-Type': 'application/json',
|
127 |
+
'Accept': 'application/json',
|
128 |
+
'Authorization': f'Bearer {api_key_token}'
|
129 |
+
}
|
130 |
+
print(f"Starting upload for: {image_path}")
|
131 |
if not os.path.exists(image_path):
|
132 |
print(f"File does not exist: {image_path}")
|
133 |
return None
|
|
|
146 |
with open(image_path, 'rb') as img_file:
|
147 |
upload_response = requests.put(put_url, data=img_file, headers=headers_put)
|
148 |
print(f"PUT response: {upload_response.status_code} - {upload_response.text}")
|
149 |
+
if upload_response.status_code not in [200, 203]:
|
|
|
|
|
|
|
|
|
150 |
raise Exception(f"PUT failed with status {upload_response.status_code}: {upload_response.text}")
|
151 |
+
if upload_response.status_code == 203:
|
152 |
+
print("Warning: PUT returned 203 - CallbackFailed, but proceeding with resourceId")
|
153 |
|
154 |
resource_id = resource_response.get('resourceId')
|
155 |
if not resource_id:
|
156 |
print(f"Upload failed - No 'resourceId' in response: {resource_response}")
|
157 |
return None
|
158 |
print(f"Upload successful - resourceId: {resource_id}")
|
159 |
+
time.sleep(10) # Đợi đồng bộ tài nguyên
|
160 |
+
print(f"Waited 10s for resource sync: {resource_id}")
|
161 |
return resource_id
|
162 |
except Exception as e:
|
163 |
print(f"Upload error for {image_path}: {str(e)}")
|
164 |
return None
|
165 |
|
166 |
+
# Hàm kiểm tra params
|
167 |
def check_workflow_params(params):
|
|
|
168 |
url = f"{url_pre}/jobs/workflow/params/check"
|
169 |
+
headers = {
|
170 |
+
'Content-Type': 'application/json',
|
171 |
+
'Accept': 'application/json',
|
172 |
+
'Authorization': f'Bearer {api_key_token}'
|
173 |
+
}
|
174 |
payload = json.dumps({"params": params})
|
175 |
+
print(f"Checking workflow params: {json.dumps(payload, indent=2)}")
|
176 |
response = requests.post(url, headers=headers, data=payload, timeout=30)
|
177 |
print(f"Params check response: {response.status_code} - {response.text}")
|
178 |
if response.status_code != 200:
|
179 |
raise Exception(f"Params check failed: {response.text}")
|
180 |
return response.json()
|
181 |
|
182 |
+
# Hàm chạy workflow và chờ kết quả
|
183 |
def run_workflow(payload, step_name):
|
|
|
|
|
184 |
try:
|
185 |
+
check_workflow_params(payload["params"])
|
|
|
186 |
except Exception as e:
|
187 |
print(f"Workflow params check failed for {step_name}: {str(e)}")
|
188 |
raise
|
189 |
+
|
190 |
+
headers = {
|
191 |
+
'Content-Type': 'application/json',
|
192 |
+
'Accept': 'application/json',
|
193 |
+
'Authorization': f'Bearer {api_key_token}'
|
194 |
+
}
|
195 |
+
print(f"Sending {step_name} workflow request to {url_pre}/jobs/workflow with data: {json.dumps(payload, indent=2)}")
|
196 |
response = requests.post(f"{url_pre}/jobs/workflow", json=payload, headers=headers, timeout=300)
|
197 |
+
print(f"{step_name} workflow response: {response.status_code} - {response.text}")
|
198 |
if response.status_code != 200:
|
199 |
raise Exception(f"Error {response.status_code}: {response.text}")
|
200 |
+
|
201 |
response_data = response.json()
|
202 |
job_id = response_data['job'].get('id')
|
203 |
if not job_id:
|
204 |
raise Exception("Không tìm thấy job_id trong response")
|
|
|
205 |
|
206 |
+
print(f"Starting {step_name} job status check for job_id: {job_id}")
|
207 |
max_attempts = 36
|
208 |
for attempt in range(max_attempts):
|
|
|
209 |
response = requests.get(f"{url_pre}/jobs/{job_id}", headers=headers, timeout=30)
|
|
|
210 |
response.raise_for_status()
|
211 |
result = response.json()
|
212 |
+
print(f"{step_name} job status (attempt {attempt + 1}/{max_attempts}): {json.dumps(result, indent=2)}")
|
213 |
status = result['job'].get('status')
|
|
|
214 |
if status == 'SUCCESS':
|
215 |
success_info = result['job'].get('successInfo', {})
|
216 |
if not success_info.get('images'):
|
217 |
raise Exception(f"Không tìm thấy hình ảnh trong successInfo cho {step_name}")
|
218 |
image_url = success_info['images'][0]['url']
|
|
|
219 |
image_response = requests.get(image_url)
|
220 |
image = Image.open(BytesIO(image_response.content))
|
221 |
+
# Chuyển từ RGBA sang RGB nếu cần
|
222 |
if image.mode == 'RGBA':
|
223 |
image = image.convert('RGB')
|
|
|
224 |
output_path = Path(SAVE_DIR) / f"{step_name}_{int(time.time())}.jpg"
|
225 |
image.save(output_path)
|
226 |
print(f"{step_name} image saved to: {output_path}")
|
|
|
228 |
elif status in ['FAILED', 'ERROR']:
|
229 |
failed_info = result['job'].get('failedInfo', {})
|
230 |
error_reason = failed_info.get('reason', 'Không có chi tiết')
|
231 |
+
error_code = failed_info.get('code', 'Không xác định')
|
232 |
+
raise Exception(f"{step_name} job thất bại: {error_reason} (code: {error_code})")
|
233 |
time.sleep(5)
|
234 |
raise Exception(f"Hết thời gian chờ {step_name} job sau 3 phút")
|
235 |
|
236 |
+
# Hàm tạo mask và áp texture
|
237 |
+
def generate_mask(image_resource_id, position, selected_product_code):
|
238 |
+
try:
|
239 |
+
if not image_resource_id:
|
240 |
+
raise Exception("Không có image_resource_id hợp lệ - ảnh gốc chưa được upload")
|
241 |
+
print(f"Using image_resource_id: {image_resource_id}")
|
242 |
+
time.sleep(10) # Đợi đồng bộ tài nguyên
|
243 |
+
|
244 |
+
short_code = selected_product_code.split()[0]
|
245 |
+
texture_filepath = PRODUCT_IMAGE_MAP.get(selected_product_code)
|
246 |
+
print(f"Texture file: {texture_filepath}, exists: {os.path.exists(texture_filepath)}")
|
247 |
+
if not texture_filepath or not os.path.exists(texture_filepath):
|
248 |
+
raise Exception(f"Không tìm thấy ảnh sản phẩm cho mã {short_code}")
|
249 |
+
|
250 |
+
texture_resource_id = upload_image_to_tensorart(texture_filepath)
|
251 |
+
print(f"Texture resource_id: {texture_resource_id}")
|
252 |
+
if not texture_resource_id:
|
253 |
+
raise Exception(f"Không thể upload ảnh sản phẩm {short_code}")
|
254 |
+
time.sleep(10) # Đợi đồng bộ tài nguyên
|
255 |
+
|
256 |
+
if isinstance(position, (set, list)):
|
257 |
+
position = position[0] if position else "default"
|
258 |
+
print(f"Position: {position}, type: {type(position)}")
|
259 |
+
|
260 |
+
# Dùng params đúng như mẫu TensorArt
|
261 |
+
workflow_params = {
|
262 |
+
"1": {
|
263 |
+
"classType": "LayerMask: SegmentAnythingUltra V3",
|
264 |
+
"inputs": {
|
265 |
+
"black_point": 0.3,
|
266 |
+
"detail_dilate": 6,
|
267 |
+
"detail_erode": 65,
|
268 |
+
"detail_method": "GuidedFilter",
|
269 |
+
"device": "cuda",
|
270 |
+
"image": ["2", 0],
|
271 |
+
"max_megapixels": 2,
|
272 |
+
"process_detail": True,
|
273 |
+
"prompt": ["4", 0],
|
274 |
+
"sam_models": ["3", 0],
|
275 |
+
"threshold": 0.3,
|
276 |
+
"white_point": 0.99
|
277 |
+
},
|
278 |
+
"properties": {"Node name for S&R": "LayerMask: SegmentAnythingUltra V3"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
279 |
},
|
280 |
+
"10": {
|
281 |
+
"classType": "Image Seamless Texture",
|
282 |
+
"inputs": {
|
283 |
+
"blending": 0.37,
|
284 |
+
"images": ["17", 0],
|
285 |
+
"tiled": "true",
|
286 |
+
"tiles": 2
|
287 |
+
},
|
288 |
+
"properties": {"Node name for S&R": "Image Seamless Texture"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
289 |
},
|
290 |
+
"13": {
|
291 |
+
"classType": "Paste By Mask",
|
292 |
+
"inputs": {
|
293 |
+
"image_base": ["2", 0],
|
294 |
+
"image_to_paste": ["10", 0],
|
295 |
+
"mask": ["8", 0],
|
296 |
+
"resize_behavior": "resize"
|
297 |
+
},
|
298 |
+
"properties": {"Node name for S&R": "Paste By Mask"}
|
|
|
299 |
},
|
300 |
+
"17": {
|
301 |
+
"classType": "TensorArt_LoadImage",
|
302 |
+
"inputs": {
|
303 |
+
"_height": 768,
|
304 |
+
"_width": 512,
|
305 |
+
"image": texture_resource_id,
|
306 |
+
"upload": "image"
|
307 |
+
},
|
308 |
+
"properties": {"Node name for S&R": "TensorArt_LoadImage"}
|
|
|
|
|
|
|
|
|
309 |
},
|
310 |
+
"2": {
|
311 |
+
"classType": "TensorArt_LoadImage",
|
312 |
+
"inputs": {
|
313 |
+
"_height": 1024,
|
314 |
+
"_width": 768,
|
315 |
+
"image": image_resource_id,
|
316 |
+
"upload": "image"
|
317 |
+
},
|
318 |
+
"properties": {"Node name for S&R": "TensorArt_LoadImage"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
319 |
},
|
320 |
+
"3": {
|
321 |
+
"classType": "LayerMask: LoadSegmentAnythingModels",
|
322 |
+
"inputs": {
|
323 |
+
"grounding_dino_model": "GroundingDINO_SwinB (938MB)",
|
324 |
+
"sam_model": "sam_vit_h (2.56GB)"
|
325 |
+
},
|
326 |
+
"properties": {"Node name for S&R": "LayerMask: LoadSegmentAnythingModels"}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
327 |
},
|
328 |
+
"4": {
|
329 |
+
"classType": "TensorArt_PromptText",
|
330 |
+
"inputs": {"Text": position.lower()},
|
331 |
+
"properties": {"Node name for S&R": "TensorArt_PromptText"}
|
332 |
+
},
|
333 |
+
"7": {
|
334 |
+
"classType": "PreviewImage",
|
335 |
+
"inputs": {"images": ["13", 0]},
|
336 |
+
"properties": {"Node name for S&R": "PreviewImage"}
|
337 |
+
},
|
338 |
+
"8": {
|
339 |
+
"classType": "MaskToImage",
|
340 |
+
"inputs": {"mask": ["1", 1]},
|
341 |
+
"properties": {"Node name for S&R": "MaskToImage"}
|
342 |
+
}
|
343 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
344 |
|
345 |
+
payload = {
|
346 |
+
"requestId": f"workflow_{int(time.time())}",
|
347 |
+
"params": workflow_params,
|
348 |
+
"runningNotifyUrl": ""
|
349 |
+
}
|
350 |
+
|
351 |
+
output_path = run_workflow(payload, "full_workflow")
|
352 |
+
return output_path
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
353 |
|
|
|
|
|
354 |
except Exception as e:
|
355 |
+
print(f"Mask generation error: {str(e)}")
|
356 |
+
return None
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
357 |
|
358 |
+
# Hàm xử lý img2img với spinner và progress bar
|
359 |
+
def generate_img2img(image, position, size_choice, custom_size, *product_choices):
|
360 |
+
yield None, gr.update(visible=True), gr.update(visible=True, value='<div class="progress-container"><div class="progress-bar" style="width: 0%"></div></div>'), None
|
361 |
+
|
362 |
+
if size_choice == "Custom size":
|
363 |
+
if not custom_size.strip():
|
364 |
+
yield "Vui lòng nhập kích thước tùy chỉnh.", gr.update(visible=False), gr.update(visible=False), None
|
365 |
+
return
|
366 |
+
width, height = map(int, custom_size.split("x"))
|
367 |
+
else:
|
368 |
+
width, height = map(int, size_choice.split("x"))
|
369 |
+
|
370 |
+
yield "Đang upload ảnh gốc...", gr.update(visible=True), gr.update(visible=True, value='<div class="progress-container"><div class="progress-bar" style="width: 20%"></div></div>'), None
|
371 |
+
image_path = Path(SAVE_DIR) / f"input_{int(time.time())}.jpg"
|
372 |
+
image.save(image_path)
|
373 |
+
image_resource_id = upload_image_to_tensorart(str(image_path))
|
374 |
+
print(f"Generated image_resource_id: {image_resource_id}")
|
375 |
+
if not image_resource_id:
|
376 |
+
yield "Lỗi: Không thể upload ảnh gốc", gr.update(visible=False), gr.update(visible=False), None
|
377 |
+
return
|
378 |
+
|
379 |
+
yield "Đang chuẩn bị ảnh sản phẩm...", gr.update(visible=True), gr.update(visible=True, value='<div class="progress-container"><div class="progress-bar" style="width: 40%"></div></div>'), None
|
380 |
+
selected_products = []
|
381 |
+
for group, choices in zip(PRODUCT_GROUPS.keys(), product_choices):
|
382 |
+
selected_products.extend(choices)
|
383 |
+
if not selected_products:
|
384 |
+
yield "Vui lòng chọn ít nhất một mã sản phẩm.", gr.update(visible=False), gr.update(visible=False), None
|
385 |
+
return
|
386 |
+
selected_product_code = selected_products[0]
|
387 |
+
|
388 |
+
yield "Đang tạo mask và áp texture...", gr.update(visible=True), gr.update(visible=True, value='<div class="progress-container"><div class="progress-bar" style="width: 60%"></div></div>'), None
|
389 |
+
output_path = generate_mask(image_resource_id, position, selected_product_code)
|
390 |
+
if not output_path:
|
391 |
+
yield "Lỗi: Không thể tạo ảnh", gr.update(visible=False), gr.update(visible=False), None
|
392 |
+
return
|
393 |
|
394 |
+
yield "Hoàn tất!", gr.update(visible=False), gr.update(visible=True, value='<div class="progress-container"><div class="progress-bar" style="width: 100%"></div></div>'), Image.open(output_path)
|
|
|
|
|
|
|
395 |
|
396 |
+
# Hàm generate_with_loading (text2img)
|
397 |
+
def generate_with_loading(prompt, size_choice, custom_size, *product_choices):
|
398 |
yield None, gr.update(visible=True), gr.update(visible=True, value='<div class="progress-container"><div class="progress-bar" style="width: 0%"></div></div>'), None
|
399 |
try:
|
|
|
400 |
if size_choice == "Custom size":
|
401 |
if not custom_size.strip():
|
|
|
402 |
yield "Vui lòng nhập kích thước tùy chỉnh.", gr.update(visible=False), gr.update(visible=False), None
|
403 |
return
|
404 |
width, height = map(int, custom_size.split("x"))
|
|
|
405 |
else:
|
406 |
width, height = map(int, size_choice.split("x"))
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
407 |
|
408 |
selected_products = []
|
409 |
for group, choices in zip(PRODUCT_GROUPS.keys(), product_choices):
|
410 |
selected_products.extend(choices)
|
411 |
if not selected_products:
|
|
|
412 |
yield "Vui lòng chọn ít nhất một mã sản phẩm.", gr.update(visible=False), gr.update(visible=False), None
|
413 |
return
|
|
|
|
|
414 |
|
415 |
+
short_codes = [code.split()[0] for code in selected_products]
|
416 |
+
rewritten_prompt = rewrite_prompt_with_groq(prompt, short_codes)
|
417 |
+
print(f"Rewritten Prompt: {rewritten_prompt}")
|
418 |
+
|
419 |
+
progress = 0
|
420 |
+
while progress < 100:
|
421 |
+
time.sleep(0.5)
|
422 |
+
progress += 10
|
423 |
+
yield None, gr.update(visible=True), gr.update(value=f'<div class="progress-container"><div class="progress-bar" style="width: {progress}%"></div></div>'), None
|
424 |
+
|
425 |
+
result = txt2img(rewritten_prompt, width, height, short_codes)
|
426 |
+
if isinstance(result, str):
|
427 |
+
yield result, gr.update(visible=False), gr.update(visible=False), None
|
428 |
+
else:
|
429 |
+
yield None, gr.update(visible=False), gr.update(visible=False), result
|
430 |
except Exception as e:
|
|
|
431 |
yield f"Lỗi khi xử lý: {e}", gr.update(visible=False), gr.update(visible=False), None
|
432 |
|
433 |
+
# Hàm text2img
|
434 |
+
def txt2img(prompt, width, height, product_codes):
|
435 |
+
model_id = "779398605850080514"
|
436 |
+
vae_id = "ae.sft"
|
437 |
+
|
438 |
+
txt2img_data = {
|
439 |
+
"request_id": hashlib.md5(str(int(time.time())).encode()).hexdigest(),
|
440 |
+
"stages": [
|
441 |
+
{"type": "INPUT_INITIALIZE", "inputInitialize": {"seed": -1, "count": 1}},
|
442 |
+
{
|
443 |
+
"type": "DIFFUSION",
|
444 |
+
"diffusion": {
|
445 |
+
"width": width,
|
446 |
+
"height": height,
|
447 |
+
"prompts": [{"text": prompt}],
|
448 |
+
"negativePrompts": [{"text": " "}],
|
449 |
+
"sdModel": model_id,
|
450 |
+
"sdVae": vae_id,
|
451 |
+
"sampler": "Euler a",
|
452 |
+
"steps": 30,
|
453 |
+
"cfgScale": 8,
|
454 |
+
"clipSkip": 1,
|
455 |
+
"etaNoiseSeedDelta": 31337,
|
456 |
+
}
|
457 |
+
}
|
458 |
+
]
|
459 |
+
}
|
460 |
+
headers = {
|
461 |
+
'Content-Type': 'application/json',
|
462 |
+
'Accept': 'application/json',
|
463 |
+
'Authorization': f'Bearer {api_key_token}'
|
464 |
+
}
|
465 |
+
response = requests.post(f"{url_pre}/jobs", json=txt2img_data, headers=headers)
|
466 |
+
if response.status_code != 200:
|
467 |
+
return f"Error: {response.status_code} - {response.text}"
|
468 |
+
response_data = response.json()
|
469 |
+
job_id = response_data['job']['id']
|
470 |
+
print(f"Job created. ID: {job_id}")
|
471 |
+
start_time = time.time()
|
472 |
+
timeout = 300
|
473 |
+
while True:
|
474 |
+
time.sleep(10)
|
475 |
+
elapsed_time = time.time() - start_time
|
476 |
+
if elapsed_time > timeout:
|
477 |
+
return f"Error: Job timed out after {timeout} seconds."
|
478 |
+
response = requests.get(f"{url_pre}/jobs/{job_id}", headers=headers)
|
479 |
+
if response.status_code != 200:
|
480 |
+
return f"Error: {response.status_code} - {response.text}"
|
481 |
+
get_job_response_data = response.json()
|
482 |
+
job_status = get_job_response_data['job']['status']
|
483 |
+
if job_status == 'SUCCESS':
|
484 |
+
image_url = get_job_response_data['job']['successInfo']['images'][0]['url']
|
485 |
+
response_image = requests.get(image_url)
|
486 |
+
img = Image.open(BytesIO(response_image.content))
|
487 |
+
save_path = Path(SAVE_DIR) / f"{hashlib.md5(prompt.encode()).hexdigest()}.png"
|
488 |
+
img.save(save_path)
|
489 |
+
print(f"Image saved to: {save_path}")
|
490 |
+
return img
|
491 |
+
elif job_status == 'FAILED':
|
492 |
+
return "Error: Job failed."
|
493 |
+
|
494 |
+
# CSS
|
495 |
css = """
|
496 |
.loading-spinner { border: 4px solid #f3f3f3; border-top: 4px solid #3498db; border-radius: 50%; width: 40px; height: 40px; animation: spin 1s linear infinite; margin: auto; }
|
497 |
@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
|
498 |
.progress-container { width: 100%; max-width: 400px; margin: 20px auto; background-color: #f3f3f3; border-radius: 20px; overflow: hidden; box-shadow: 0 2px 5px rgba(0, 0, 0, 0.1); }
|
499 |
.progress-bar { width: 0%; height: 10px; background: linear-gradient(90deg, #3498db, #e74c3c); border-radius: 20px; transition: width 0.3s ease-in-out; }
|
500 |
+
/* Thêm vào phần CSS hiện có */
|
501 |
+
.image-row {
|
502 |
+
display: flex;
|
503 |
+
gap: 20px;
|
504 |
+
margin-bottom: 20px;
|
505 |
+
}
|
506 |
+
|
507 |
+
.image-container {
|
508 |
+
flex: 1;
|
509 |
+
border: 1px solid #ddd;
|
510 |
+
padding: 10px;
|
511 |
+
border-radius: 5px;
|
512 |
+
}
|
513 |
+
|
514 |
+
.settings-column {
|
515 |
+
max-width: 400px;
|
516 |
+
margin-left: 20px;
|
517 |
+
}
|
518 |
+
|
519 |
+
.position-selector {
|
520 |
+
margin-top: 20px;
|
521 |
+
padding: 15px;
|
522 |
+
background: #f5f5f5;
|
523 |
+
border-radius: 5px;
|
524 |
+
}
|
525 |
"""
|
526 |
|
527 |
+
# Giao diện Gradio
|
528 |
with gr.Blocks(css=css) as demo:
|
529 |
+
gr.Markdown("## Ứng dụng Tạo Ảnh CaslaQuartz với Ảnh Sản Phẩm")
|
530 |
with gr.Tabs():
|
531 |
+
with gr.Tab("Text2Img"):
|
532 |
with gr.Row():
|
533 |
with gr.Column():
|
534 |
+
prompt_input = gr.Textbox(label="Mô tả ảnh cần tạo", placeholder="Một nhà bếp hiện đại với mặt bàn bằng đá thạch anh.")
|
535 |
+
size_radio = gr.Radio(choices=["1152x768", "1024x1024", "768x1152", "Custom size"], label="Chọn kích thước ảnh", value="1024x1024")
|
536 |
+
custom_size_input = gr.Textbox(label="Nhập kích thước tùy chỉnh (VD: 1280x720)", placeholder="Chiều rộng x Chiều cao", visible=False)
|
537 |
+
size_radio.change(fn=lambda x: gr.update(visible=x == "Custom size"), inputs=size_radio, outputs=custom_size_input)
|
538 |
+
product_checkbox_group = []
|
539 |
for group, color in GROUP_COLORS.items():
|
540 |
with gr.Accordion(f"Sản phẩm - {group}", open=False):
|
541 |
checkboxes = gr.CheckboxGroup(
|
|
|
543 |
label=f"Chọn sản phẩm ({group})",
|
544 |
value=[]
|
545 |
)
|
546 |
+
product_checkbox_group.append((group, checkboxes))
|
547 |
+
generate_button = gr.Button("Generate")
|
548 |
with gr.Column():
|
549 |
+
output_image = gr.Image(label="Ảnh đã tạo")
|
550 |
+
error_message = gr.Textbox(label="Thông báo", visible=False)
|
551 |
+
loading_spinner = gr.HTML('<div class="loading-spinner"></div>', visible=False)
|
552 |
+
progress_bar = gr.HTML('', visible=False)
|
553 |
+
inputs = [prompt_input, size_radio, custom_size_input] + [checkboxes for _, checkboxes in product_checkbox_group]
|
554 |
+
generate_button.click(fn=generate_with_loading, inputs=inputs, outputs=[error_message, loading_spinner, progress_bar, output_image])
|
555 |
+
|
556 |
+
with gr.Tab("Img2Img"):
|
|
|
|
|
|
|
|
|
|
|
557 |
with gr.Row():
|
558 |
+
# Cột ảnh input và output
|
559 |
+
with gr.Column():
|
560 |
+
gr.Markdown("### Ảnh đầu vào và kết quả")
|
561 |
+
with gr.Row():
|
562 |
+
image_upload = gr.Image(label="Ảnh đầu vào", type="pil", height=400)
|
563 |
+
output_image_img2img = gr.Image(label="Ảnh kết quả", height=400)
|
564 |
+
|
565 |
+
# Phần chọn vật ngay dưới ảnh input
|
566 |
+
with gr.Row():
|
567 |
+
with gr.Column():
|
568 |
+
position_input = gr.Dropdown(
|
569 |
+
label="Vị trí áp dụng",
|
570 |
+
choices=["Wall", "Countertop", "Floor", "Counter", "Table", "Coffee Table top", "Backsplash"],
|
571 |
+
value="Wall"
|
572 |
+
)
|
573 |
+
|
574 |
+
# Cột cài đặt và chọn sản phẩm
|
575 |
with gr.Column():
|
576 |
+
gr.Markdown("### Cài đặt")
|
577 |
+
with gr.Accordion("Cài đặt kích thước", open=False):
|
578 |
+
size_radio_img2img = gr.Radio(
|
579 |
+
label="Kích thước ảnh",
|
580 |
+
choices=["1152x768", "1024x1024", "768x1152", "Custom size"],
|
581 |
+
value="1024x1024"
|
582 |
+
)
|
583 |
+
custom_size_input_img2img = gr.Textbox(
|
584 |
+
label="Kích thước tùy chỉnh",
|
585 |
+
placeholder="Chiều rộng x Chiều cao (VD: 1280x720)",
|
586 |
+
visible=False
|
587 |
+
)
|
588 |
+
size_radio_img2img.change(
|
589 |
+
fn=lambda x: gr.update(visible=x == "Custom size"),
|
590 |
+
inputs=size_radio_img2img,
|
591 |
+
outputs=custom_size_input_img2img
|
592 |
+
)
|
593 |
+
|
594 |
+
gr.Markdown("### Chọn sản phẩm")
|
595 |
+
product_checkbox_group_img2img = []
|
596 |
for group, color in GROUP_COLORS.items():
|
597 |
with gr.Accordion(f"Sản phẩm - {group}", open=False):
|
598 |
checkboxes = gr.CheckboxGroup(
|
599 |
+
choices=[(code, code) for code in PRODUCT_GROUPS[group]
|
600 |
+
if PRODUCT_GROUPS[group][code] not in ["is coming", "is loading"]],
|
601 |
label=f"Chọn sản phẩm ({group})",
|
602 |
+
value=[],
|
603 |
+
elem_classes=["product-select"]
|
604 |
)
|
605 |
+
product_checkbox_group_img2img.append((group, checkboxes))
|
606 |
+
|
607 |
+
# Phần thông báo và nút bấm
|
608 |
+
with gr.Row():
|
609 |
+
error_message_img2img = gr.Textbox(label="Thông báo", visible=False)
|
610 |
+
loading_spinner_img2img = gr.HTML(
|
611 |
+
'<div class="loading-spinner"></div>',
|
612 |
+
visible=False
|
613 |
+
)
|
614 |
+
progress_bar_img2img = gr.HTML(
|
615 |
+
'<div class="progress-container"><div class="progress-bar"></div></div>',
|
616 |
+
visible=False
|
617 |
+
)
|
618 |
+
inpaint_button = gr.Button("Tạo Ảnh", variant="primary")
|
619 |
+
|
620 |
+
# Inputs and button click handler
|
621 |
+
inputs_img2img = [image_upload, position_input, size_radio_img2img, custom_size_input_img2img] + \
|
622 |
+
[checkboxes for _, checkboxes in product_checkbox_group_img2img]
|
623 |
+
|
624 |
+
inpaint_button.click(
|
625 |
+
fn=generate_img2img,
|
626 |
+
inputs=inputs_img2img,
|
627 |
+
outputs=[error_message_img2img, loading_spinner_img2img, progress_bar_img2img, output_image_img2img]
|
628 |
+
)
|
629 |
|
630 |
demo.launch(share=True)
|