Spaces:
Sleeping
Sleeping
Upload 3 files
Browse files- Dockerfile +10 -0
- app.py +557 -0
- 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
|