TDN-M commited on
Commit
d491f2a
·
verified ·
1 Parent(s): 6bff867

Upload 3 files

Browse files
Files changed (3) hide show
  1. Dockerfile +10 -0
  2. app.py +557 -0
  3. requirements.txt +8 -0
Dockerfile ADDED
@@ -0,0 +1,10 @@
 
 
 
 
 
 
 
 
 
 
 
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"]
app.py ADDED
@@ -0,0 +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
+
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 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",
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)
requirements.txt ADDED
@@ -0,0 +1,8 @@
 
 
 
 
 
 
 
 
 
1
+ gradio>=3.50.0
2
+ requests>=2.31.0
3
+ Pillow>=10.0.0
4
+ python-dotenv>=1.0.0
5
+ groq>=0.3.0
6
+ rembg>=2.0.50
7
+ opencv-python-headless>=4.8.0
8
+ numpy>=1.24.0