TDN-M commited on
Commit
097485b
·
verified ·
1 Parent(s): e1ad34d

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +9 -12
  2. README.md +27 -10
  3. app.py +556 -559
Dockerfile CHANGED
@@ -1,13 +1,10 @@
1
- FROM python:3.9-slim
2
-
3
- # Thêm user không phải root
4
- RUN adduser --disabled-password --gecos "" appuser
5
- USER appuser
6
-
7
- # Tiếp tục sao chép mã nguồn và chạy ứng dụng
8
- WORKDIR /app
9
- COPY requirements.txt .
10
- RUN pip install --no-cache-dir -r requirements.txt
11
- COPY . .
12
-
13
  CMD ["python", "app.py"]
 
1
+ FROM python:3.9-slim
2
+
3
+ WORKDIR /app
4
+
5
+ COPY requirements.txt .
6
+ RUN pip install --no-cache-dir -r requirements.txt
7
+
8
+ COPY . .
9
+
 
 
 
10
  CMD ["python", "app.py"]
README.md CHANGED
@@ -1,10 +1,27 @@
1
- ---
2
- title: CaslaQuartz
3
- emoji: 🏃
4
- colorFrom: yellow
5
- colorTo: purple
6
- sdk: docker
7
- pinned: false
8
- ---
9
-
10
- Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Calsaquartz App
3
+ emoji: 🚀
4
+ colorFrom: blue
5
+ colorTo: purple
6
+ sdk: gradio
7
+ sdk_version: "5.17.0"
8
+ app_file: app.py
9
+ pinned: false
10
+ ---
11
+
12
+ Calsaquartz Application
13
+
14
+ This is a demo application hosted on Hugging Face Spaces. It uses Gradio for the interface and runs on Docker.
15
+
16
+ How to Run Locally
17
+ 1. Clone this repository:
18
+ git clone https://huggingface.co/spaces/yTDN-M/Calsaquartz
19
+ cd Calsaquartz
20
+
21
+ 2. Install dependencies:
22
+ pip install -r requirements.txt
23
+
24
+ 3. Run the app:
25
+ python app.py
26
+
27
+ 4. Access the app at http://localhost:7860
app.py CHANGED
@@ -1,560 +1,557 @@
1
- import gradio as gr
2
- import requests
3
- import json
4
- import os
5
- import hashlib
6
- import time
7
- from PIL import Image
8
- from pathlib import Path
9
- from io import BytesIO
10
- from groq import Groq
11
- from dotenv import load_dotenv
12
- from pathlib import Path
13
-
14
-
15
- if os.path.exists('.env'):
16
- load_dotenv()
17
- else:
18
- print("Warning: .env file not found. Using default environment variables.")
19
-
20
- # Get environment variables with fallback
21
- api_key_token = os.getenv('api_key_token', '')
22
- groq_api_key = os.getenv('groq_api_key', '')
23
-
24
- if not api_key_token or not groq_api_key:
25
- raise ValueError("Please configure your API keys in .env file")
26
-
27
- # Ghi log khởi động
28
- print("Version 2.19 - Fixed RGBA to RGB conversion for JPEG")
29
- print("Running grok_app.py with workflow processing and .env configuration")
30
-
31
- # URL và thông tin cá nhân từ API
32
- url_pre = "https://ap-east-1.tensorart.cloud/v1"
33
- # Đường dẫn tương đối đến thư mục gốc
34
- SAVE_DIR = Path("generated_images")
35
- SAVE_DIR.mkdir(exist_ok=True)
36
-
37
- # Danh sách mã sản phẩm
38
- PRODUCT_GROUPS = {
39
- "Standard": {
40
- "C1012 Glacier White": "817687427545199895",
41
- "C1026 Polar": "819910519797326073",
42
- "C3269 Ash Grey": "821839484099264081",
43
- "C3168 Silver Wave": "821849044696643212",
44
- "C1005 Milky White": "821948258441171133",
45
- },
46
- "Deluxe": {
47
- "C2103 Onyx Carrara": "827090618489513527",
48
- "C2104 Massa": "822075428127644644",
49
- "C3105 Casla Cloudy": "828912225788997963",
50
- "C3146 Casla Nova": "828013009961087650",
51
- "C2240 Marquin": "828085015087780649",
52
- "C2262 Concrete (Honed)": "822211862058871636",
53
- "C3311 Calacatta Sky": "829984593223502930",
54
- "C3346 Massimo": "827938741386607132",
55
- },
56
- "Luxury": {
57
- "C4143 Mario": "829984593223502930",
58
- "C4145 Marina": "828132560375742058",
59
- "C4202 Calacatta Gold": "828167757632695310",
60
- "C1205 Casla Everest": "828296778450463190",
61
- "C4211 Calacatta Supreme": "828436321937882328",
62
- "C4204 Calacatta Classic": "828422973179466146",
63
- "C5240 Spring": "is coming",
64
- "C1102 Super White": "828545723344775887",
65
- "C4246 Casla Mystery": "828544778451950698",
66
- "C4345 Oro": "828891068780182635",
67
- "C4346 Luxe": "829436426547535131",
68
- "C4342 Casla Eternal": "829190256201829181",
69
- "C4221 Athena": "829644354504131520",
70
- "C4222 Lagoon": "is coming",
71
- "C5225 Amber": "is coming",
72
- },
73
- "Super Luxury": {
74
- "C4255 Calacatta Extra": "829659013227537217",
75
- },
76
- }
77
-
78
- # Danh sách ảnh sản phẩm tương ứng với mã sản phẩm
79
- PRODUCT_IMAGE_MAP = {
80
- "C1012 Glacier White": "product_images/C1012.jpg",
81
- "C1026 Polar": "product_images/C1026.jpg",
82
- "C3269 Ash Grey": "product_images/C3269.jpg",
83
- "C3168 Silver Wave": "product_images/C3168.jpg",
84
- "C1005 Milky White": "product_images/C1005.jpg",
85
- "C2103 Onyx Carrara": "product_images/C2103.jpg",
86
- "C2104 Massa": "product_images/C2104.jpg",
87
- "C3105 Casla Cloudy": "product_images/C3105.jpg",
88
- "C3146 Casla Nova": "product_images/C3146.jpg",
89
- "C2240 Marquin": "product_images/C2240.jpg",
90
- "C2262 Concrete (Honed)": "product_images/C2262.jpg",
91
- "C3311 Calacatta Sky": "product_images/C3311.jpg",
92
- "C3346 Massimo": "product_images/C3346.jpg",
93
- "C4143 Mario": "product_images/C4143.jpg",
94
- "C4145 Marina": "product_images/C4145.jpg",
95
- "C4202 Calacatta Gold": "product_images/C4202.jpg",
96
- "C1205 Casla Everest": "product_images/C1205.jpg",
97
- "C4211 Calacatta Supreme": "product_images/C4211.jpg",
98
- "C4204 Calacatta Classic": "product_images/C4204.jpg",
99
- "C1102 Super White": "product_images/C1102.jpg",
100
- "C4246 Casla Mystery": "product_images/C4246.jpg",
101
- "C4345 Oro": "product_images/C4345.jpg",
102
- "C4346 Luxe": "product_images/C4346.jpg",
103
- "C4342 Casla Eternal": "product_images/C4342.jpg",
104
- "C4221 Athena": "product_images/C4221.jpg",
105
- "C4255 Calacatta Extra": "product_images/C4255.jpg",
106
- }
107
-
108
- # Định nghĩa màu sắc cho từng nhóm sản phẩm
109
- GROUP_COLORS = {
110
- "Standard": "#FFCCCC",
111
- "Deluxe": "#CCFFCC",
112
- "Luxury": "#CCCCFF",
113
- "Super Luxury": "#CCFCFF",
114
- }
115
-
116
- # Khởi tạo client Groq
117
- client = Groq(api_key=groq_api_key)
118
-
119
- def rewrite_prompt_with_groq(vietnamese_prompt, product_codes):
120
- prompt = f"{vietnamese_prompt}, featuring {' and '.join(product_codes)} quartz marble"
121
- return prompt
122
-
123
- # Hàm upload ảnh lên TensorArt
124
- def upload_image_to_tensorart(image_path):
125
- try:
126
- url = f"{url_pre}/resource/image"
127
- payload = json.dumps({"expireSec": "7200"})
128
- headers = {
129
- 'Content-Type': 'application/json',
130
- 'Accept': 'application/json',
131
- 'Authorization': f'Bearer {api_key_token}'
132
- }
133
- print(f"Starting upload for: {image_path}")
134
- if not os.path.exists(image_path):
135
- print(f"File does not exist: {image_path}")
136
- return None
137
- response = requests.post(url, headers=headers, data=payload, timeout=30)
138
- print(f"POST response: {response.status_code} - {response.text}")
139
- response.raise_for_status()
140
- resource_response = response.json()
141
-
142
- put_url = resource_response.get('putUrl')
143
- headers_put = resource_response.get('headers', {'Content-Type': 'image/jpeg'})
144
- if not put_url:
145
- print(f"Upload failed - No 'putUrl' in response: {resource_response}")
146
- return None
147
-
148
- print(f"Got putUrl: {put_url}")
149
- with open(image_path, 'rb') as img_file:
150
- upload_response = requests.put(put_url, data=img_file, headers=headers_put)
151
- print(f"PUT response: {upload_response.status_code} - {upload_response.text}")
152
- if upload_response.status_code not in [200, 203]:
153
- raise Exception(f"PUT failed with status {upload_response.status_code}: {upload_response.text}")
154
- if upload_response.status_code == 203:
155
- print("Warning: PUT returned 203 - CallbackFailed, but proceeding with resourceId")
156
-
157
- resource_id = resource_response.get('resourceId')
158
- if not resource_id:
159
- print(f"Upload failed - No 'resourceId' in response: {resource_response}")
160
- return None
161
- print(f"Upload successful - resourceId: {resource_id}")
162
- time.sleep(10) # Đợi đồng bộ tài nguyên
163
- print(f"Waited 10s for resource sync: {resource_id}")
164
- return resource_id
165
- except Exception as e:
166
- print(f"Upload error for {image_path}: {str(e)}")
167
- return None
168
-
169
- # Hàm kiểm tra params
170
- def check_workflow_params(params):
171
- url = f"{url_pre}/jobs/workflow/params/check"
172
- headers = {
173
- 'Content-Type': 'application/json',
174
- 'Accept': 'application/json',
175
- 'Authorization': f'Bearer {api_key_token}'
176
- }
177
- payload = json.dumps({"params": params})
178
- print(f"Checking workflow params: {json.dumps(payload, indent=2)}")
179
- response = requests.post(url, headers=headers, data=payload, timeout=30)
180
- print(f"Params check response: {response.status_code} - {response.text}")
181
- if response.status_code != 200:
182
- raise Exception(f"Params check failed: {response.text}")
183
- return response.json()
184
-
185
- # Hàm chạy workflow và chờ kết quả
186
- def run_workflow(payload, step_name):
187
- try:
188
- check_workflow_params(payload["params"])
189
- except Exception as e:
190
- print(f"Workflow params check failed for {step_name}: {str(e)}")
191
- raise
192
-
193
- headers = {
194
- 'Content-Type': 'application/json',
195
- 'Accept': 'application/json',
196
- 'Authorization': f'Bearer {api_key_token}'
197
- }
198
- print(f"Sending {step_name} workflow request to {url_pre}/jobs/workflow with data: {json.dumps(payload, indent=2)}")
199
- response = requests.post(f"{url_pre}/jobs/workflow", json=payload, headers=headers, timeout=300)
200
- print(f"{step_name} workflow response: {response.status_code} - {response.text}")
201
- if response.status_code != 200:
202
- raise Exception(f"Error {response.status_code}: {response.text}")
203
-
204
- response_data = response.json()
205
- job_id = response_data['job'].get('id')
206
- if not job_id:
207
- raise Exception("Không tìm thấy job_id trong response")
208
-
209
- print(f"Starting {step_name} job status check for job_id: {job_id}")
210
- max_attempts = 36
211
- for attempt in range(max_attempts):
212
- response = requests.get(f"{url_pre}/jobs/{job_id}", headers=headers, timeout=30)
213
- response.raise_for_status()
214
- result = response.json()
215
- print(f"{step_name} job status (attempt {attempt + 1}/{max_attempts}): {json.dumps(result, indent=2)}")
216
- status = result['job'].get('status')
217
- if status == 'SUCCESS':
218
- success_info = result['job'].get('successInfo', {})
219
- if not success_info.get('images'):
220
- raise Exception(f"Không tìm thấy hình ảnh trong successInfo cho {step_name}")
221
- image_url = success_info['images'][0]['url']
222
- image_response = requests.get(image_url)
223
- image = Image.open(BytesIO(image_response.content))
224
- # Chuyển từ RGBA sang RGB nếu cần
225
- if image.mode == 'RGBA':
226
- image = image.convert('RGB')
227
- output_path = Path(SAVE_DIR) / f"{step_name}_{int(time.time())}.jpg"
228
- image.save(output_path)
229
- print(f"{step_name} image saved to: {output_path}")
230
- return str(output_path)
231
- elif status in ['FAILED', 'ERROR']:
232
- failed_info = result['job'].get('failedInfo', {})
233
- error_reason = failed_info.get('reason', 'Không có chi tiết')
234
- error_code = failed_info.get('code', 'Không xác định')
235
- raise Exception(f"{step_name} job thất bại: {error_reason} (code: {error_code})")
236
- time.sleep(5)
237
- raise Exception(f"Hết thời gian chờ {step_name} job sau 3 phút")
238
-
239
- # Hàm tạo mask và áp texture
240
- def generate_mask(image_resource_id, position, selected_product_code):
241
- try:
242
- if not image_resource_id:
243
- raise Exception("Không có image_resource_id hợp lệ - ảnh gốc chưa được upload")
244
- print(f"Using image_resource_id: {image_resource_id}")
245
- time.sleep(10) # Đợi đồng bộ tài nguyên
246
-
247
- short_code = selected_product_code.split()[0]
248
- texture_filepath = PRODUCT_IMAGE_MAP.get(selected_product_code)
249
- print(f"Texture file: {texture_filepath}, exists: {os.path.exists(texture_filepath)}")
250
- if not texture_filepath or not os.path.exists(texture_filepath):
251
- raise Exception(f"Không tìm thấy ảnh sản phẩm cho mã {short_code}")
252
-
253
- texture_resource_id = upload_image_to_tensorart(texture_filepath)
254
- print(f"Texture resource_id: {texture_resource_id}")
255
- if not texture_resource_id:
256
- raise Exception(f"Không thể upload ảnh sản phẩm {short_code}")
257
- time.sleep(10) # Đợi đồng bộ tài nguyên
258
-
259
- if isinstance(position, (set, list)):
260
- position = position[0] if position else "default"
261
- print(f"Position: {position}, type: {type(position)}")
262
-
263
- # Dùng params đúng như mẫu TensorArt
264
- workflow_params = {
265
- "1": {
266
- "classType": "LayerMask: SegmentAnythingUltra V3",
267
- "inputs": {
268
- "black_point": 0.3,
269
- "detail_dilate": 6,
270
- "detail_erode": 65,
271
- "detail_method": "GuidedFilter",
272
- "device": "cuda",
273
- "image": ["2", 0],
274
- "max_megapixels": 2,
275
- "process_detail": True,
276
- "prompt": ["4", 0],
277
- "sam_models": ["3", 0],
278
- "threshold": 0.3,
279
- "white_point": 0.99
280
- },
281
- "properties": {"Node name for S&R": "LayerMask: SegmentAnythingUltra V3"}
282
- },
283
- "10": {
284
- "classType": "Image Seamless Texture",
285
- "inputs": {
286
- "blending": 0.37,
287
- "images": ["17", 0],
288
- "tiled": "true",
289
- "tiles": 2
290
- },
291
- "properties": {"Node name for S&R": "Image Seamless Texture"}
292
- },
293
- "13": {
294
- "classType": "Paste By Mask",
295
- "inputs": {
296
- "image_base": ["2", 0],
297
- "image_to_paste": ["10", 0],
298
- "mask": ["8", 0],
299
- "resize_behavior": "resize"
300
- },
301
- "properties": {"Node name for S&R": "Paste By Mask"}
302
- },
303
- "17": {
304
- "classType": "TensorArt_LoadImage",
305
- "inputs": {
306
- "_height": 768,
307
- "_width": 512,
308
- "image": texture_resource_id,
309
- "upload": "image"
310
- },
311
- "properties": {"Node name for S&R": "TensorArt_LoadImage"}
312
- },
313
- "2": {
314
- "classType": "TensorArt_LoadImage",
315
- "inputs": {
316
- "_height": 1024,
317
- "_width": 768,
318
- "image": image_resource_id,
319
- "upload": "image"
320
- },
321
- "properties": {"Node name for S&R": "TensorArt_LoadImage"}
322
- },
323
- "3": {
324
- "classType": "LayerMask: LoadSegmentAnythingModels",
325
- "inputs": {
326
- "grounding_dino_model": "GroundingDINO_SwinB (938MB)",
327
- "sam_model": "sam_vit_h (2.56GB)"
328
- },
329
- "properties": {"Node name for S&R": "LayerMask: LoadSegmentAnythingModels"}
330
- },
331
- "4": {
332
- "classType": "TensorArt_PromptText",
333
- "inputs": {"Text": position.lower()},
334
- "properties": {"Node name for S&R": "TensorArt_PromptText"}
335
- },
336
- "7": {
337
- "classType": "PreviewImage",
338
- "inputs": {"images": ["13", 0]},
339
- "properties": {"Node name for S&R": "PreviewImage"}
340
- },
341
- "8": {
342
- "classType": "MaskToImage",
343
- "inputs": {"mask": ["1", 1]},
344
- "properties": {"Node name for S&R": "MaskToImage"}
345
- }
346
- }
347
-
348
- payload = {
349
- "requestId": f"workflow_{int(time.time())}",
350
- "params": workflow_params,
351
- "runningNotifyUrl": ""
352
- }
353
-
354
- output_path = run_workflow(payload, "full_workflow")
355
- return output_path
356
-
357
- except Exception as e:
358
- print(f"Mask generation error: {str(e)}")
359
- return None
360
-
361
- # Hàm xử lý img2img với spinner và progress bar
362
- def generate_img2img(image, position, size_choice, custom_size, *product_choices):
363
- 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
364
-
365
- if size_choice == "Custom size":
366
- if not custom_size.strip():
367
- yield "Vui lòng nhập kích thước tùy chỉnh.", gr.update(visible=False), gr.update(visible=False), None
368
- return
369
- width, height = map(int, custom_size.split("x"))
370
- else:
371
- width, height = map(int, size_choice.split("x"))
372
-
373
- 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
374
- image_path = Path(SAVE_DIR) / f"input_{int(time.time())}.jpg"
375
- image.save(image_path)
376
- image_resource_id = upload_image_to_tensorart(str(image_path))
377
- print(f"Generated image_resource_id: {image_resource_id}")
378
- if not image_resource_id:
379
- yield "Lỗi: Không thể upload ảnh gốc", gr.update(visible=False), gr.update(visible=False), None
380
- return
381
-
382
- 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
383
- selected_products = []
384
- for group, choices in zip(PRODUCT_GROUPS.keys(), product_choices):
385
- selected_products.extend(choices)
386
- if not selected_products:
387
- 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
388
- return
389
- selected_product_code = selected_products[0]
390
-
391
- yield "Đang tạo mask á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
392
- output_path = generate_mask(image_resource_id, position, selected_product_code)
393
- if not output_path:
394
- yield "Lỗi: Không thể tạo ảnh", gr.update(visible=False), gr.update(visible=False), None
395
- return
396
-
397
- 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)
398
-
399
- # Hàm generate_with_loading (text2img)
400
- def generate_with_loading(prompt, size_choice, custom_size, *product_choices):
401
- 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
402
- try:
403
- if size_choice == "Custom size":
404
- if not custom_size.strip():
405
- yield "Vui lòng nhập kích thước tùy chỉnh.", gr.update(visible=False), gr.update(visible=False), None
406
- return
407
- width, height = map(int, custom_size.split("x"))
408
- else:
409
- width, height = map(int, size_choice.split("x"))
410
-
411
- selected_products = []
412
- for group, choices in zip(PRODUCT_GROUPS.keys(), product_choices):
413
- selected_products.extend(choices)
414
- if not selected_products:
415
- 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
416
- return
417
-
418
- short_codes = [code.split()[0] for code in selected_products]
419
- rewritten_prompt = rewrite_prompt_with_groq(prompt, short_codes)
420
- print(f"Rewritten Prompt: {rewritten_prompt}")
421
-
422
- progress = 0
423
- while progress < 100:
424
- time.sleep(0.5)
425
- progress += 10
426
- yield None, gr.update(visible=True), gr.update(value=f'<div class="progress-container"><div class="progress-bar" style="width: {progress}%"></div></div>'), None
427
-
428
- result = txt2img(rewritten_prompt, width, height, short_codes)
429
- if isinstance(result, str):
430
- yield result, gr.update(visible=False), gr.update(visible=False), None
431
- else:
432
- yield None, gr.update(visible=False), gr.update(visible=False), result
433
- except Exception as e:
434
- yield f"Lỗi khi xử lý: {e}", gr.update(visible=False), gr.update(visible=False), None
435
-
436
- # Hàm text2img
437
- def txt2img(prompt, width, height, product_codes):
438
- model_id = "779398605850080514"
439
- vae_id = "ae.sft"
440
-
441
- txt2img_data = {
442
- "request_id": hashlib.md5(str(int(time.time())).encode()).hexdigest(),
443
- "stages": [
444
- {"type": "INPUT_INITIALIZE", "inputInitialize": {"seed": -1, "count": 1}},
445
- {
446
- "type": "DIFFUSION",
447
- "diffusion": {
448
- "width": width,
449
- "height": height,
450
- "prompts": [{"text": prompt}],
451
- "negativePrompts": [{"text": " "}],
452
- "sdModel": model_id,
453
- "sdVae": vae_id,
454
- "sampler": "Euler a",
455
- "steps": 30,
456
- "cfgScale": 8,
457
- "clipSkip": 1,
458
- "etaNoiseSeedDelta": 31337,
459
- }
460
- }
461
- ]
462
- }
463
- headers = {
464
- 'Content-Type': 'application/json',
465
- 'Accept': 'application/json',
466
- 'Authorization': f'Bearer {api_key_token}'
467
- }
468
- response = requests.post(f"{url_pre}/jobs", json=txt2img_data, headers=headers)
469
- if response.status_code != 200:
470
- return f"Error: {response.status_code} - {response.text}"
471
- response_data = response.json()
472
- job_id = response_data['job']['id']
473
- print(f"Job created. ID: {job_id}")
474
- start_time = time.time()
475
- timeout = 300
476
- while True:
477
- time.sleep(10)
478
- elapsed_time = time.time() - start_time
479
- if elapsed_time > timeout:
480
- return f"Error: Job timed out after {timeout} seconds."
481
- response = requests.get(f"{url_pre}/jobs/{job_id}", headers=headers)
482
- if response.status_code != 200:
483
- return f"Error: {response.status_code} - {response.text}"
484
- get_job_response_data = response.json()
485
- job_status = get_job_response_data['job']['status']
486
- if job_status == 'SUCCESS':
487
- image_url = get_job_response_data['job']['successInfo']['images'][0]['url']
488
- response_image = requests.get(image_url)
489
- img = Image.open(BytesIO(response_image.content))
490
- save_path = Path(SAVE_DIR) / f"{hashlib.md5(prompt.encode()).hexdigest()}.png"
491
- img.save(save_path)
492
- print(f"Image saved to: {save_path}")
493
- return img
494
- elif job_status == 'FAILED':
495
- return "Error: Job failed."
496
-
497
- # CSS
498
- css = """
499
- .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; }
500
- @keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }
501
- .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); }
502
- .progress-bar { width: 0%; height: 10px; background: linear-gradient(90deg, #3498db, #e74c3c); border-radius: 20px; transition: width 0.3s ease-in-out; }
503
- """
504
-
505
- # Giao diện Gradio
506
- with gr.Blocks(css=css) as demo:
507
- gr.Markdown("## Ứng dụng Tạo Ảnh CaslaQuartz với Ảnh Sản Phẩm")
508
- with gr.Tabs():
509
- with gr.Tab("Text2Img"):
510
- with gr.Row():
511
- with gr.Column():
512
- 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.")
513
- size_radio = gr.Radio(choices=["1152x768", "1024x1024", "768x1152", "Custom size"], label="Chọn kích thước ảnh", value="1024x1024")
514
- 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)
515
- size_radio.change(fn=lambda x: gr.update(visible=x == "Custom size"), inputs=size_radio, outputs=custom_size_input)
516
- product_checkbox_group = []
517
- for group, color in GROUP_COLORS.items():
518
- with gr.Accordion(f"Sản phẩm - {group}", open=False):
519
- checkboxes = gr.CheckboxGroup(
520
- choices=[(code, code) for code in PRODUCT_GROUPS[group] if PRODUCT_GROUPS[group][code] not in ["is coming", "is loading"]],
521
- label=f"Chọn sản phẩm ({group})",
522
- value=[]
523
- )
524
- product_checkbox_group.append((group, checkboxes))
525
- generate_button = gr.Button("Generate")
526
- with gr.Column():
527
- output_image = gr.Image(label="Ảnh đã tạo")
528
- error_message = gr.Textbox(label="Thông báo", visible=False)
529
- loading_spinner = gr.HTML('<div class="loading-spinner"></div>', visible=False)
530
- progress_bar = gr.HTML('', visible=False)
531
- inputs = [prompt_input, size_radio, custom_size_input] + [checkboxes for _, checkboxes in product_checkbox_group]
532
- generate_button.click(fn=generate_with_loading, inputs=inputs, outputs=[error_message, loading_spinner, progress_bar, output_image])
533
-
534
- with gr.Tab("Img2Img"):
535
- with gr.Row():
536
- with gr.Column():
537
- image_upload = gr.Image(label="Tải lên ảnh", type="pil")
538
- position_input = gr.Dropdown(label="Chọn vật muốn thử nghiệm", choices=["Wall", "Countertop", "Floor", "Backsplash"], value="Wall")
539
- size_radio_img2img = gr.Radio(choices=["1152x768", "1024x1024", "768x1152", "Custom size"], label="Chọn kích thước ảnh", value="1024x1024")
540
- custom_size_input_img2img = 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)
541
- size_radio_img2img.change(fn=lambda x: gr.update(visible=x == "Custom size"), inputs=size_radio_img2img, outputs=custom_size_input_img2img)
542
- product_checkbox_group_img2img = []
543
- for group, color in GROUP_COLORS.items():
544
- with gr.Accordion(f"Sản phẩm - {group}", open=False):
545
- checkboxes = gr.CheckboxGroup(
546
- choices=[(code, code) for code in PRODUCT_GROUPS[group] if PRODUCT_GROUPS[group][code] not in ["is coming", "is loading"]],
547
- label=f"Chọn sản phẩm ({group})",
548
- value=[]
549
- )
550
- product_checkbox_group_img2img.append((group, checkboxes))
551
- inpaint_button = gr.Button("Inpaint")
552
- with gr.Column():
553
- output_image_img2img = gr.Image(label="Ảnh đã tạo")
554
- error_message_img2img = gr.Textbox(label="Thông báo", visible=False)
555
- loading_spinner_img2img = gr.HTML('<div class="loading-spinner"></div>', visible=False)
556
- progress_bar_img2img = gr.HTML('', visible=False)
557
- inputs_img2img = [image_upload, position_input, size_radio_img2img, custom_size_input_img2img] + [checkboxes for _, checkboxes in product_checkbox_group_img2img]
558
- inpaint_button.click(fn=generate_img2img, inputs=inputs_img2img, outputs=[error_message_img2img, loading_spinner_img2img, progress_bar_img2img, output_image_img2img])
559
-
560
  demo.launch(share=True)
 
1
+ import gradio as gr
2
+ import requests
3
+ import json
4
+ import os
5
+ import hashlib
6
+ import time
7
+ from PIL import Image
8
+ from pathlib import Path
9
+ from io import BytesIO
10
+ from groq import Groq
11
+ from dotenv import load_dotenv
12
+
13
+ if os.path.exists('.env'):
14
+ load_dotenv()
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 thông tin 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",
109
+ "Luxury": "#CCCCFF",
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
134
+ response = requests.post(url, headers=headers, data=payload, timeout=30)
135
+ print(f"POST response: {response.status_code} - {response.text}")
136
+ response.raise_for_status()
137
+ resource_response = response.json()
138
+
139
+ put_url = resource_response.get('putUrl')
140
+ headers_put = resource_response.get('headers', {'Content-Type': 'image/jpeg'})
141
+ if not put_url:
142
+ print(f"Upload failed - No 'putUrl' in response: {resource_response}")
143
+ return None
144
+
145
+ print(f"Got putUrl: {put_url}")
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}")
227
+ return str(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
+ """
501
+
502
+ # Giao diện Gradio
503
+ with gr.Blocks(css=css) as demo:
504
+ gr.Markdown("## Ứng dụng Tạo Ảnh CaslaQuartz với Ảnh Sản Phẩm")
505
+ with gr.Tabs():
506
+ with gr.Tab("Text2Img"):
507
+ with gr.Row():
508
+ with gr.Column():
509
+ 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.")
510
+ size_radio = gr.Radio(choices=["1152x768", "1024x1024", "768x1152", "Custom size"], label="Chọn kích thước ảnh", value="1024x1024")
511
+ 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)
512
+ size_radio.change(fn=lambda x: gr.update(visible=x == "Custom size"), inputs=size_radio, outputs=custom_size_input)
513
+ product_checkbox_group = []
514
+ for group, color in GROUP_COLORS.items():
515
+ with gr.Accordion(f"Sản phẩm - {group}", open=False):
516
+ checkboxes = gr.CheckboxGroup(
517
+ choices=[(code, code) for code in PRODUCT_GROUPS[group] if PRODUCT_GROUPS[group][code] not in ["is coming", "is loading"]],
518
+ label=f"Chọn sản phẩm ({group})",
519
+ value=[]
520
+ )
521
+ product_checkbox_group.append((group, checkboxes))
522
+ generate_button = gr.Button("Generate")
523
+ with gr.Column():
524
+ output_image = gr.Image(label="Ảnh đã tạo")
525
+ error_message = gr.Textbox(label="Thông báo", visible=False)
526
+ loading_spinner = gr.HTML('<div class="loading-spinner"></div>', visible=False)
527
+ progress_bar = gr.HTML('', visible=False)
528
+ inputs = [prompt_input, size_radio, custom_size_input] + [checkboxes for _, checkboxes in product_checkbox_group]
529
+ generate_button.click(fn=generate_with_loading, inputs=inputs, outputs=[error_message, loading_spinner, progress_bar, output_image])
530
+
531
+ with gr.Tab("Img2Img"):
532
+ with gr.Row():
533
+ with gr.Column():
534
+ image_upload = gr.Image(label="Tải lên ảnh", type="pil")
535
+ position_input = gr.Dropdown(label="Chọn vật muốn thử nghiệm", choices=["Wall", "Countertop", "Floor", "Backsplash"], value="Wall")
536
+ size_radio_img2img = gr.Radio(choices=["1152x768", "1024x1024", "768x1152", "Custom size"], label="Chọn kích thước ảnh", value="1024x1024")
537
+ custom_size_input_img2img = 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)
538
+ size_radio_img2img.change(fn=lambda x: gr.update(visible=x == "Custom size"), inputs=size_radio_img2img, outputs=custom_size_input_img2img)
539
+ product_checkbox_group_img2img = []
540
+ for group, color in GROUP_COLORS.items():
541
+ with gr.Accordion(f"Sản phẩm - {group}", open=False):
542
+ checkboxes = gr.CheckboxGroup(
543
+ choices=[(code, code) for code in PRODUCT_GROUPS[group] if PRODUCT_GROUPS[group][code] not in ["is coming", "is loading"]],
544
+ label=f"Chọn sản phẩm ({group})",
545
+ value=[]
546
+ )
547
+ product_checkbox_group_img2img.append((group, checkboxes))
548
+ inpaint_button = gr.Button("Inpaint")
549
+ with gr.Column():
550
+ output_image_img2img = gr.Image(label="Ảnh đã tạo")
551
+ error_message_img2img = gr.Textbox(label="Thông báo", visible=False)
552
+ loading_spinner_img2img = gr.HTML('<div class="loading-spinner"></div>', visible=False)
553
+ progress_bar_img2img = gr.HTML('', visible=False)
554
+ inputs_img2img = [image_upload, position_input, size_radio_img2img, custom_size_input_img2img] + [checkboxes for _, checkboxes in product_checkbox_group_img2img]
555
+ inpaint_button.click(fn=generate_img2img, inputs=inputs_img2img, outputs=[error_message_img2img, loading_spinner_img2img, progress_bar_img2img, output_image_img2img])
556
+
 
 
 
557
  demo.launch(share=True)