TDN-M commited on
Commit
ece00f8
·
verified ·
1 Parent(s): 783eb91

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +440 -427
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
- print(f"===== Application Startup at {time.strftime('%Y-%m-%d %H:%M:%S')} =====")
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": {"C1012 Glacier White": "817687427545199895", "C1026 Polar": "819910519797326073", "C3269 Ash Grey": "821839484099264081", "C3168 Silver Wave": "821849044696643212", "C1005 Milky White": "821948258441171133"},
34
- "Deluxe": {"C2103 Onyx Carrara": "827090618489513527", "C2104 Massa": "822075428127644644", "C3105 Casla Cloudy": "828912225788997963", "C3146 Casla Nova": "828013009961087650", "C2240 Marquin": "828085015087780649", "C2262 Concrete (Honed)": "822211862058871636", "C3311 Calacatta Sky": "829984593223502930", "C3346 Massimo": "827938741386607132"},
35
- "Luxury": {"C4143 Mario": "829984593223502930", "C4145 Marina": "828132560375742058", "C4202 Calacatta Gold": "828167757632695310", "C1205 Casla Everest": "828296778450463190", "C4211 Calacatta Supreme": "828436321937882328", "C4204 Calacatta Classic": "828422973179466146", "C5240 Spring": "is coming", "C1102 Super White": "828545723344775887", "C4246 Casla Mystery": "828544778451950698", "C4345 Oro": "828891068780182635", "C4346 Luxe": "829436426547535131", "C4342 Casla Eternal": "829190256201829181", "C4221 Athena": "829644354504131520", "C4222 Lagoon": "is coming", "C5225 Amber": "is coming"},
36
- "Super Luxury": {"C4255 Calacatta Extra": "829659013227537217"},
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
  }
38
 
 
39
  PRODUCT_IMAGE_MAP = {
40
- "C1012 Glacier White": "product_images/C1012.jpg", "C1026 Polar": "product_images/C1026.jpg", "C3269 Ash Grey": "product_images/C3269.jpg", "C3168 Silver Wave": "product_images/C3168.jpg", "C1005 Milky White": "product_images/C1005.jpg",
41
- "C2103 Onyx Carrara": "product_images/C2103.jpg", "C2104 Massa": "product_images/C2104.jpg", "C3105 Casla Cloudy": "product_images/C3105.jpg", "C3146 Casla Nova": "product_images/C3146.jpg", "C2240 Marquin": "product_images/C2240.jpg",
42
- "C2262 Concrete (Honed)": "product_images/C2262.jpg", "C3311 Calacatta Sky": "product_images/C3311.jpg", "C3346 Massimo": "product_images/C3346.jpg", "C4143 Mario": "product_images/C4143.jpg", "C4145 Marina": "product_images/C4145.jpg",
43
- "C4202 Calacatta Gold": "product_images/C4202.jpg", "C1205 Casla Everest": "product_images/C1205.jpg", "C4211 Calacatta Supreme": "product_images/C4211.jpg", "C4204 Calacatta Classic": "product_images/C4204.jpg", "C1102 Super White": "product_images/C1102.jpg",
44
- "C4246 Casla Mystery": "product_images/C4246.jpg", "C4345 Oro": "product_images/C4345.jpg", "C4346 Luxe": "product_images/C4346.jpg", "C4342 Casla Eternal": "product_images/C4342.jpg", "C4221 Athena": "product_images/C4221.jpg",
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- print(f"Rewriting prompt: {vietnamese_prompt} with product codes: {product_codes}")
59
- system_prompt = (
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 = {'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': f'Bearer {api_key_token}'}
87
- print(f"Sending POST request to: {url}")
 
 
 
 
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 == 200:
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 = {'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': f'Bearer {api_key_token}'}
 
 
 
 
128
  payload = json.dumps({"params": params})
129
- print(f"Sending params check request to: {url}")
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
- check_result = check_workflow_params(payload["params"])
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
- headers = {'Content-Type': 'application/json', 'Accept': 'application/json', 'Authorization': f'Bearer {api_key_token}'}
146
- print(f"Submitting workflow to: {url_pre}/jobs/workflow")
 
 
 
 
 
147
  response = requests.post(f"{url_pre}/jobs/workflow", json=payload, headers=headers, timeout=300)
148
- print(f"Workflow submission response: {response.status_code} - {response.text}")
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
- raise Exception(f"{step_name} job thất bại: {error_reason}")
 
185
  time.sleep(5)
186
  raise Exception(f"Hết thời gian chờ {step_name} job sau 3 phút")
187
 
188
- def generate_text2img_workflow(prompt, width, height, selected_product_code):
189
- print(f"Starting Text2Img workflow with prompt: {prompt}, width: {width}, height: {height}, product_code: {selected_product_code}")
190
- texture_filepath = PRODUCT_IMAGE_MAP.get(selected_product_code)
191
- if not texture_filepath or not os.path.exists(texture_filepath):
192
- print(f"Error: Texture file not found for product code {selected_product_code}")
193
- raise Exception(f"Không tìm thấy ảnh sản phẩm cho mã {selected_product_code}")
194
- texture_resource_id = upload_image_to_tensorart(texture_filepath)
195
- if not texture_resource_id:
196
- print(f"Error: Failed to upload texture for product code {selected_product_code}")
197
- raise Exception(f"Không thể upload ảnh sản phẩm {selected_product_code}")
198
- print(f"Texture uploaded successfully, resource_id: {texture_resource_id}")
199
-
200
- workflow_params = {
201
- "27": {
202
- "classType": "EmptySD3LatentImage",
203
- "inputs": {"width": width, "height": height, "batch_size": 1},
204
- "properties": {"Node name for S&R": "EmptySD3LatentImage"}
205
- },
206
- "45": {
207
- "classType": "TensorArt_PromptText",
208
- "inputs": {"Text": prompt},
209
- "properties": {"Node name for S&R": "TensorArt_PromptText"}
210
- },
211
- "42": {
212
- "classType": "CLIPTextEncode",
213
- "inputs": {"clip": ["44", 1], "text": ["45", 0]},
214
- "properties": {"Node name for S&R": "CLIPTextEncode"}
215
- },
216
- "41": {
217
- "classType": "CLIPTextEncode",
218
- "inputs": {"clip": ["44", 1], "text": ""},
219
- "properties": {"Node name for S&R": "CLIPTextEncode"}
220
- },
221
- "35": {
222
- "classType": "FluxGuidance",
223
- "inputs": {"conditioning": ["42", 0], "guidance": 3.5},
224
- "properties": {"Node name for S&R": "FluxGuidance"}
225
- },
226
- "47": {
227
- "classType": "TensorArt_Seed",
228
- "inputs": {"seed": int(time.time()), "mode": "randomize"},
229
- "properties": {"Node name for S&R": "TensorArt_Seed"}
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
- "properties": {"Node name for S&R": "KSampler"}
246
- },
247
- "8": {
248
- "classType": "VAEDecode",
249
- "inputs": {"samples": ["31", 0], "vae": ["43", 2]},
250
- "properties": {"Node name for S&R": "VAEDecode"}
251
- },
252
- "43": {
253
- "classType": "TensorArt_CheckpointLoader",
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
- "properties": {"Node name for S&R": "Lora Loader"}
267
- },
268
- "44": {
269
- "classType": "TensorArt_LoraLoader",
270
- "inputs": {
271
- "model": ["56", 0],
272
- "clip": ["56", 1],
273
- "lora_name": "821795980375725476",
274
- "strength_model": 0.64,
275
- "strength_clip": 1
276
  },
277
- "properties": {"Node name for S&R": "TensorArt_LoraLoader"}
278
- },
279
- "49": {
280
- "classType": "TensorArt_LLM_Prompt(Free)",
281
- "inputs": {
282
- "prompt": ["45", 0],
283
- "system_prompt": "You are the best assistant and you help choosing exactly the objects from user's input.\nExample:\nUser's input: \"Serene corner with quartz marble countertop centered amidst soft lighting and calming surroundings, capturing warmth and tranquility\".\nYour answer: \"countertop\"\n\n*Remember that you will only response the objects name without anything else.",
284
- "model": "deepseek-ai/DeepSeek-R1-Distill-Qwen-7B",
285
- "seed": 941451825154147,
286
- "mode": "randomize",
287
- "max_images": 1,
288
- "max_tokens": 4093,
289
- "censor": "disable"
290
  },
291
- "properties": {"Node name for S&R": "TensorArt_LLM_Prompt(Free)"}
292
- },
293
- "54": {
294
- "classType": "TensorArt_LoadImage",
295
- "inputs": {"image": texture_resource_id, "upload": "image"},
296
- "properties": {"Node name for S&R": "TensorArt_LoadImage"}
297
- },
298
- "53": {
299
- "classType": "Image Seamless Texture",
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
- "properties": {"Node name for S&R": "LayerMask: SegmentAnythingUltra V3"}
320
- },
321
- "51": {
322
- "classType": "LayerMask: LoadSegmentAnythingModels",
323
- "inputs": {"sam_model": "sam_vit_h (2.56GB)", "grounding_dino_model": "GroundingDINO_SwinT_OGC (694MB)"},
324
- "properties": {"Node name for S&R": "LayerMask: LoadSegmentAnythingModels"}
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
- "properties": {"Node name for S&R": "SaveImage"}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
- def generate_text2img_with_loading(prompt, size_choice, custom_size, *product_choices):
359
- print("Starting Text2Img generation with loading")
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
- try:
362
- print(f"Processing size choice: {size_choice}, custom_size: {custom_size}")
363
- if size_choice == "Custom size":
364
- if not custom_size.strip():
365
- print("Error: Custom size input is empty")
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"Text2Img generation failed: {str(e)}")
395
- yield f"Lỗi khi xử lý: {e}", gr.update(visible=False), gr.update(visible=False), None
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
- payload = {
494
- "requestId": f"workflow_{int(time.time())}",
495
- "params": workflow_params,
496
- "runningNotifyUrl": ""
497
- }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
498
 
499
- print("Preparing to run Img2Img workflow")
500
- output_path = run_workflow(payload, "img2img")
501
- print(f"Img2Img workflow completed, output_path: {output_path}")
502
- return output_path
503
 
504
- def generate_img2img_with_loading(image, position, size_choice, custom_size, *product_choices):
505
- print("Starting Img2Img generation with loading")
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
- output_path = generate_img2img_workflow(image_resource_id, position, selected_product_code)
541
- if not output_path:
542
- print("Error: Workflow returned no output")
543
- yield "Lỗi: Không thể tạo ảnh", gr.update(visible=False), gr.update(visible=False), None
544
- return
545
-
546
- print("Img2Img generation completed successfully")
547
- 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)
 
 
 
 
 
 
 
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
- .image-row { display: flex; gap: 20px; margin-bottom: 20px; }
558
- .image-container { flex: 1; border: 1px solid #ddd; padding: 10px; border-radius: 5px; }
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
559
  """
560
 
 
561
  with gr.Blocks(css=css) as demo:
562
- gr.Markdown("## CaslaQuartz AI")
563
  with gr.Tabs():
564
- with gr.Tab("Tạo hình ảnh ứng dụng sản phẩm đá CaslaQuartz"):
565
  with gr.Row():
566
  with gr.Column():
567
- text2img_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.")
568
- text2img_size_radio = gr.Radio(choices=["1152x768", "1024x1024", "768x1152", "Custom size"], label="Chọn kích thước ảnh", value="1024x1024")
569
- text2img_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)
570
- text2img_size_radio.change(fn=lambda x: gr.update(visible=x == "Custom size"), inputs=text2img_size_radio, outputs=text2img_custom_size_input)
571
- text2img_product_checkbox_group = []
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
- text2img_product_checkbox_group.append((group, checkboxes))
580
- text2img_generate_button = gr.Button("Generate")
581
  with gr.Column():
582
- text2img_output_image = gr.Image(label="Ảnh đã tạo")
583
- text2img_error_message = gr.Textbox(label="Thông báo", visible=False)
584
- text2img_loading_spinner = gr.HTML('<div class="loading-spinner"></div>', visible=False)
585
- text2img_progress_bar = gr.HTML('', visible=False)
586
- text2img_inputs = [text2img_prompt_input, text2img_size_radio, text2img_custom_size_input] + [checkboxes for _, checkboxes in text2img_product_checkbox_group]
587
- text2img_generate_button.click(fn=generate_text2img_with_loading, inputs=text2img_inputs, outputs=[text2img_error_message, text2img_loading_spinner, text2img_progress_bar, text2img_output_image])
588
-
589
- with gr.Tab("Ứng dụng sản phẩm CaslaQuartz trong không gian bất kỳ"):
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
- img2img_size_radio = gr.Radio(choices=["1152x768", "1024x1024", "768x1152", "Custom size"], label="Chọn kích thước ảnh", value="1024x1024")
598
- img2img_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)
599
- img2img_size_radio.change(fn=lambda x: gr.update(visible=x == "Custom size"), inputs=img2img_size_radio, outputs=img2img_custom_size_input)
600
- img2img_product_checkbox_group = []
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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] if PRODUCT_GROUPS[group][code] not in ["is coming", "is loading"]],
 
605
  label=f"Chọn sản phẩm ({group})",
606
- value=[]
 
607
  )
608
- img2img_product_checkbox_group.append((group, checkboxes))
609
- img2img_generate_button = gr.Button("Generate")
610
- with gr.Column():
611
- img2img_error_message = gr.Textbox(label="Thông báo", visible=False)
612
- img2img_loading_spinner = gr.HTML('<div class="loading-spinner"></div>', visible=False)
613
- img2img_progress_bar = gr.HTML('', visible=False)
614
- img2img_inputs = [img2img_image_upload, img2img_position_input, img2img_size_radio, img2img_custom_size_input] + [checkboxes for _, checkboxes in img2img_product_checkbox_group]
615
- img2img_generate_button.click(fn=generate_img2img_with_loading, inputs=img2img_inputs, outputs=[img2img_error_message, img2img_loading_spinner, img2img_progress_bar, img2img_output_image])
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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 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 */
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)