Spaces:
Running
Running
Update main.py
Browse files
main.py
CHANGED
|
@@ -1098,58 +1098,70 @@ def approve_project_plan(project_id):
|
|
| 1098 |
return jsonify({'error': 'Project not found or access denied'}), 404
|
| 1099 |
|
| 1100 |
selected_option = request.json.get('selectedOption')
|
|
|
|
| 1101 |
|
|
|
|
|
|
|
| 1102 |
try:
|
| 1103 |
response = requests.get(project_data['userImageURL'], timeout=30)
|
| 1104 |
response.raise_for_status()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1105 |
pil_image = Image.open(io.BytesIO(response.content)).convert('RGB')
|
| 1106 |
except Exception as e:
|
| 1107 |
-
logger.error(f"[PROJECT APPROVAL] Image
|
| 1108 |
return jsonify({'error': 'Failed to process project image'}), 500
|
| 1109 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1110 |
context = (
|
| 1111 |
f"The user chose the upcycling project: '{selected_option}'."
|
| 1112 |
if selected_option
|
| 1113 |
else f"The user has approved the plan for '{project_data['projectTitle']}'."
|
| 1114 |
)
|
| 1115 |
|
| 1116 |
-
# ------------------------------------------------------------------
|
| 1117 |
-
# Expanded system prompt for 2026 – covers fashion, electronics, etc.
|
| 1118 |
-
# ------------------------------------------------------------------
|
| 1119 |
detailed_prompt = f"""
|
| 1120 |
-
You are
|
| 1121 |
-
project titled "{project_data['projectTitle']}" in the category "{project_data.get('category', 'DIY')}".
|
| 1122 |
{context}
|
| 1123 |
-
|
| 1124 |
-
Provide a detailed, practical guide. For EVERY step you MUST provide a clear illustrative image
|
| 1125 |
-
that shows the action being performed.
|
| 1126 |
-
|
| 1127 |
-
Category-specific guidance:
|
| 1128 |
-
- Fashion & Clothing Repair: specify stitch type, thread colour, needle gauge, and fabric handling tips.
|
| 1129 |
-
- Electronics & PCB Repair: include ESD safety warnings, soldering temperatures, and component reference numbers.
|
| 1130 |
-
- Beauty & Personal Care DIY: note skin-type suitability and patch-test reminders.
|
| 1131 |
-
- Furniture Restoration: mention wood grain direction, sanding grits, and cure times.
|
| 1132 |
-
- All categories: prioritise safety, sustainability, and locally available materials.
|
| 1133 |
-
|
| 1134 |
Format your response EXACTLY like this:
|
| 1135 |
TOOLS AND MATERIALS:
|
| 1136 |
-
- Tool
|
| 1137 |
-
-
|
| 1138 |
-
STEPS
|
| 1139 |
1. First step instructions.
|
| 1140 |
-
2. Second step instructions.
|
| 1141 |
"""
|
|
|
|
|
|
|
| 1142 |
|
| 1143 |
try:
|
| 1144 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1145 |
model=GENERATION_MODEL,
|
| 1146 |
config=types.GenerateContentConfig(response_modalities=["Text", "Image"])
|
| 1147 |
)
|
| 1148 |
full_resp = chat.send_message([detailed_prompt, pil_image])
|
| 1149 |
-
ai_time
|
| 1150 |
logger.info(f"[PROJECT APPROVAL] AI generation completed in {ai_time:.3f}s")
|
| 1151 |
|
| 1152 |
-
|
|
|
|
|
|
|
|
|
|
| 1153 |
combined_text = ""
|
| 1154 |
inline_images = []
|
| 1155 |
for part in gen_parts:
|
|
@@ -1160,57 +1172,140 @@ def approve_project_plan(project_id):
|
|
| 1160 |
inline_images.append(img)
|
| 1161 |
|
| 1162 |
combined_text = combined_text.strip()
|
|
|
|
|
|
|
| 1163 |
|
| 1164 |
-
|
| 1165 |
-
|
| 1166 |
-
|
| 1167 |
-
)
|
| 1168 |
-
|
| 1169 |
-
|
| 1170 |
-
|
| 1171 |
-
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1172 |
|
| 1173 |
-
if not
|
| 1174 |
-
logger.error(f"[PROJECT APPROVAL]
|
| 1175 |
-
|
|
|
|
| 1176 |
|
| 1177 |
tools_section = tools_match.group(1).strip()
|
| 1178 |
-
steps_section = (steps_match.group(1) or steps_match.group(2)).strip()
|
| 1179 |
|
| 1180 |
-
|
| 1181 |
-
|
| 1182 |
-
|
| 1183 |
-
|
| 1184 |
-
|
| 1185 |
-
|
| 1186 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1187 |
|
| 1188 |
parsed_steps = parse_numbered_steps(steps_section)
|
| 1189 |
|
| 1190 |
-
if not tools_list
|
| 1191 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1192 |
|
| 1193 |
if len(parsed_steps) != len(inline_images):
|
| 1194 |
-
|
| 1195 |
-
|
| 1196 |
-
|
| 1197 |
-
|
| 1198 |
-
|
| 1199 |
-
|
| 1200 |
-
|
| 1201 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1202 |
try:
|
|
|
|
| 1203 |
img_byte_arr = io.BytesIO()
|
| 1204 |
-
|
| 1205 |
-
|
| 1206 |
-
|
| 1207 |
-
|
| 1208 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1209 |
except Exception as e:
|
| 1210 |
-
logger.error(f"[PROJECT APPROVAL]
|
| 1211 |
-
|
| 1212 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1213 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1214 |
update_data = {
|
| 1215 |
"status": "ready",
|
| 1216 |
"toolsList": tools_list,
|
|
@@ -1218,17 +1313,46 @@ def approve_project_plan(project_id):
|
|
| 1218 |
"selectedOption": selected_option or ""
|
| 1219 |
}
|
| 1220 |
project_ref.update(update_data)
|
| 1221 |
-
|
|
|
|
|
|
|
| 1222 |
|
|
|
|
|
|
|
| 1223 |
updated_project = project_ref.get()
|
| 1224 |
updated_project["projectId"] = project_id
|
|
|
|
|
|
|
| 1225 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1226 |
total_time = time.time() - start_time
|
| 1227 |
-
logger.info(f"[PROJECT APPROVAL] SUCCESS in {total_time:.3f}s")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1228 |
return jsonify(updated_project)
|
| 1229 |
|
| 1230 |
except Exception as e:
|
| 1231 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1232 |
return jsonify({'error': f'Internal server error: {str(e)}'}), 500
|
| 1233 |
|
| 1234 |
|
|
|
|
| 1098 |
return jsonify({'error': 'Project not found or access denied'}), 404
|
| 1099 |
|
| 1100 |
selected_option = request.json.get('selectedOption')
|
| 1101 |
+
logger.info(f"[PROJECT APPROVAL] Selected option: {selected_option}")
|
| 1102 |
|
| 1103 |
+
# Image download and processing timing
|
| 1104 |
+
image_download_start = time.time()
|
| 1105 |
try:
|
| 1106 |
response = requests.get(project_data['userImageURL'], timeout=30)
|
| 1107 |
response.raise_for_status()
|
| 1108 |
+
except requests.RequestException as e:
|
| 1109 |
+
logger.error(f"[PROJECT APPROVAL] ERROR: Image download failed: {e}")
|
| 1110 |
+
return jsonify({'error': 'Failed to download project image'}), 500
|
| 1111 |
+
|
| 1112 |
+
image_download_time = time.time() - image_download_start
|
| 1113 |
+
logger.info(f"[PROJECT APPROVAL] Image download completed in {image_download_time:.3f}s, size: {len(response.content)} bytes")
|
| 1114 |
+
|
| 1115 |
+
image_processing_start = time.time()
|
| 1116 |
+
try:
|
| 1117 |
pil_image = Image.open(io.BytesIO(response.content)).convert('RGB')
|
| 1118 |
except Exception as e:
|
| 1119 |
+
logger.error(f"[PROJECT APPROVAL] ERROR: Image processing failed: {e}")
|
| 1120 |
return jsonify({'error': 'Failed to process project image'}), 500
|
| 1121 |
|
| 1122 |
+
image_processing_time = time.time() - image_processing_start
|
| 1123 |
+
logger.info(f"[PROJECT APPROVAL] Image processing completed in {image_processing_time:.3f}s")
|
| 1124 |
+
|
| 1125 |
+
# Context preparation timing
|
| 1126 |
+
context_start = time.time()
|
| 1127 |
context = (
|
| 1128 |
f"The user chose the upcycling project: '{selected_option}'."
|
| 1129 |
if selected_option
|
| 1130 |
else f"The user has approved the plan for '{project_data['projectTitle']}'."
|
| 1131 |
)
|
| 1132 |
|
|
|
|
|
|
|
|
|
|
| 1133 |
detailed_prompt = f"""
|
| 1134 |
+
You are a DIY expert. The user wants to proceed with the project titled "{project_data['projectTitle']}".
|
|
|
|
| 1135 |
{context}
|
| 1136 |
+
Provide a detailed guide. For each step, you MUST provide a simple, clear illustrative image.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1137 |
Format your response EXACTLY like this:
|
| 1138 |
TOOLS AND MATERIALS:
|
| 1139 |
+
- Tool A
|
| 1140 |
+
- Material B
|
| 1141 |
+
STEPS(Maximum 5 steps):
|
| 1142 |
1. First step instructions.
|
| 1143 |
+
2. Second step instructions...
|
| 1144 |
"""
|
| 1145 |
+
context_time = time.time() - context_start
|
| 1146 |
+
logger.info(f"[PROJECT APPROVAL] Context preparation completed in {context_time:.3f}s")
|
| 1147 |
|
| 1148 |
try:
|
| 1149 |
+
# AI generation timing
|
| 1150 |
+
ai_start = time.time()
|
| 1151 |
+
logger.info(f"[PROJECT APPROVAL] Starting AI generation with model: {GENERATION_MODEL}")
|
| 1152 |
+
|
| 1153 |
+
chat = client.chats.create(
|
| 1154 |
model=GENERATION_MODEL,
|
| 1155 |
config=types.GenerateContentConfig(response_modalities=["Text", "Image"])
|
| 1156 |
)
|
| 1157 |
full_resp = chat.send_message([detailed_prompt, pil_image])
|
| 1158 |
+
ai_time = time.time() - ai_start
|
| 1159 |
logger.info(f"[PROJECT APPROVAL] AI generation completed in {ai_time:.3f}s")
|
| 1160 |
|
| 1161 |
+
# Response parsing timing
|
| 1162 |
+
parsing_start = time.time()
|
| 1163 |
+
gen_parts = full_resp.candidates[0].content.parts
|
| 1164 |
+
|
| 1165 |
combined_text = ""
|
| 1166 |
inline_images = []
|
| 1167 |
for part in gen_parts:
|
|
|
|
| 1172 |
inline_images.append(img)
|
| 1173 |
|
| 1174 |
combined_text = combined_text.strip()
|
| 1175 |
+
parsing_time = time.time() - parsing_start
|
| 1176 |
+
logger.info(f"[PROJECT APPROVAL] Response parsing completed in {parsing_time:.3f}s, found {len(inline_images)} images")
|
| 1177 |
|
| 1178 |
+
# Text extraction timing with robust error handling
|
| 1179 |
+
extraction_start = time.time()
|
| 1180 |
+
|
| 1181 |
+
logger.info(f"[PROJECT APPROVAL] AI Response structure check:")
|
| 1182 |
+
logger.info(f"[PROJECT APPROVAL] Full response length: {len(combined_text)}")
|
| 1183 |
+
logger.info(f"[PROJECT APPROVAL] Contains 'TOOLS AND MATERIALS': {'TOOLS AND MATERIALS' in combined_text.upper()}")
|
| 1184 |
+
logger.info(f"[PROJECT APPROVAL] Contains 'STEPS': {'STEPS' in combined_text.upper()}")
|
| 1185 |
+
logger.info(f"[PROJECT APPROVAL] Response preview: {combined_text[:300]}...")
|
| 1186 |
+
|
| 1187 |
+
tools_match = re.search(r"TOOLS AND MATERIALS:\s*(.*?)\s*(?=STEPS\s*\(|STEPS\s*:|$)", combined_text, re.DOTALL | re.IGNORECASE)
|
| 1188 |
+
steps_match = re.search(r"STEPS\s*\([^)]*\):\s*(.*)|STEPS\s*:\s*(.*)", combined_text, re.DOTALL | re.IGNORECASE)
|
| 1189 |
+
|
| 1190 |
+
if not tools_match:
|
| 1191 |
+
logger.error(f"[PROJECT APPROVAL] ERROR: Could not find TOOLS AND MATERIALS section in AI response")
|
| 1192 |
+
logger.error(f"[PROJECT APPROVAL] AI Response full text: {combined_text}")
|
| 1193 |
+
return jsonify({'error': 'AI response format error: Could not parse tools section'}), 500
|
| 1194 |
|
| 1195 |
+
if not steps_match:
|
| 1196 |
+
logger.error(f"[PROJECT APPROVAL] ERROR: Could not find STEPS section in AI response")
|
| 1197 |
+
logger.error(f"[PROJECT APPROVAL] AI Response full text: {combined_text}")
|
| 1198 |
+
return jsonify({'error': 'AI response format error: Could not parse steps section'}), 500
|
| 1199 |
|
| 1200 |
tools_section = tools_match.group(1).strip()
|
| 1201 |
+
steps_section = (steps_match.group(1) or steps_match.group(2)).strip() if steps_match else ""
|
| 1202 |
|
| 1203 |
+
if not tools_section:
|
| 1204 |
+
logger.error(f"[PROJECT APPROVAL] ERROR: Empty tools section found")
|
| 1205 |
+
return jsonify({'error': 'AI response format error: Empty tools section'}), 500
|
| 1206 |
+
|
| 1207 |
+
if not steps_section:
|
| 1208 |
+
logger.error(f"[PROJECT APPROVAL] ERROR: Empty steps section found")
|
| 1209 |
+
return jsonify({'error': 'AI response format error: Empty steps section'}), 500
|
| 1210 |
+
|
| 1211 |
+
tools_list = [line.strip("- ").strip() for line in tools_section.split('\n') if line.strip() and not line.strip().startswith('-')]
|
| 1212 |
+
dash_tools = [line.strip("- ").strip() for line in tools_section.split('\n') if line.strip().startswith('-')]
|
| 1213 |
+
tools_list.extend(dash_tools)
|
| 1214 |
+
|
| 1215 |
+
# Remove duplicates while preserving order
|
| 1216 |
+
seen = set()
|
| 1217 |
+
tools_list = [x for x in tools_list if not (x in seen or seen.add(x))]
|
| 1218 |
|
| 1219 |
parsed_steps = parse_numbered_steps(steps_section)
|
| 1220 |
|
| 1221 |
+
if not tools_list:
|
| 1222 |
+
logger.error(f"[PROJECT APPROVAL] ERROR: No tools parsed from response")
|
| 1223 |
+
logger.error(f"[PROJECT APPROVAL] Tools section was: {tools_section}")
|
| 1224 |
+
return jsonify({'error': 'AI response format error: No tools found'}), 500
|
| 1225 |
+
|
| 1226 |
+
if not parsed_steps:
|
| 1227 |
+
logger.error(f"[PROJECT APPROVAL] ERROR: No steps parsed from response")
|
| 1228 |
+
logger.error(f"[PROJECT APPROVAL] Steps section was: {steps_section}")
|
| 1229 |
+
return jsonify({'error': 'AI response format error: No steps found'}), 500
|
| 1230 |
+
|
| 1231 |
+
extraction_time = time.time() - extraction_start
|
| 1232 |
+
logger.info(f"[PROJECT APPROVAL] Text extraction completed in {extraction_time:.3f}s, tools: {len(tools_list)}, steps: {len(parsed_steps)}")
|
| 1233 |
|
| 1234 |
if len(parsed_steps) != len(inline_images):
|
| 1235 |
+
logger.error(f"[PROJECT APPROVAL] ERROR: AI response mismatch - Steps: {len(parsed_steps)}, Images: {len(inline_images)}")
|
| 1236 |
+
min_length = min(len(parsed_steps), len(inline_images))
|
| 1237 |
+
if min_length > 0:
|
| 1238 |
+
logger.info(f"[PROJECT APPROVAL] Attempting to proceed with {min_length} steps/images")
|
| 1239 |
+
parsed_steps = parsed_steps[:min_length]
|
| 1240 |
+
inline_images = inline_images[:min_length]
|
| 1241 |
+
else:
|
| 1242 |
+
return jsonify({'error': 'AI response mismatch: No valid steps and images found.'}), 500
|
| 1243 |
+
|
| 1244 |
+
# ---------------------------------------------------------------
|
| 1245 |
+
# Step processing – image upload and TTS run IN PARALLEL per step
|
| 1246 |
+
# using ThreadPoolExecutor so Nano Banana 2's longer generation
|
| 1247 |
+
# time doesn't stack on top of Deepgram latency.
|
| 1248 |
+
# ---------------------------------------------------------------
|
| 1249 |
+
import concurrent.futures
|
| 1250 |
+
|
| 1251 |
+
step_processing_start = time.time()
|
| 1252 |
+
final_steps = [None] * len(parsed_steps)
|
| 1253 |
+
total_upload_time = 0
|
| 1254 |
+
total_tts_time = 0
|
| 1255 |
+
|
| 1256 |
+
def process_single_step(args):
|
| 1257 |
+
"""Runs image upload and TTS concurrently for one step, returns completed step_info."""
|
| 1258 |
+
i, step_info, pil_img = args
|
| 1259 |
+
result = dict(step_info) # shallow copy so we don't mutate shared state
|
| 1260 |
+
img_url = ""
|
| 1261 |
+
narration_url = ""
|
| 1262 |
+
|
| 1263 |
try:
|
| 1264 |
+
# Encode JPEG in this thread before spawning sub-futures
|
| 1265 |
img_byte_arr = io.BytesIO()
|
| 1266 |
+
pil_img.save(img_byte_arr, format='JPEG', optimize=True, quality=70)
|
| 1267 |
+
img_bytes_val = img_byte_arr.getvalue()
|
| 1268 |
+
img_path = f"users/{uid}/projects/{project_id}/steps/step_{i+1}_image.jpg"
|
| 1269 |
+
|
| 1270 |
+
# Fire image upload and TTS simultaneously
|
| 1271 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=2) as inner_pool:
|
| 1272 |
+
img_upload_start = time.time()
|
| 1273 |
+
tts_start = time.time()
|
| 1274 |
+
|
| 1275 |
+
img_future = inner_pool.submit(upload_to_storage, img_bytes_val, img_path, 'image/jpeg')
|
| 1276 |
+
tts_future = inner_pool.submit(generate_tts_audio_and_upload, result['text'], uid, project_id, i + 1)
|
| 1277 |
+
|
| 1278 |
+
img_url = img_future.result()
|
| 1279 |
+
img_upload_time = time.time() - img_upload_start
|
| 1280 |
+
logger.info(f"[PROJECT APPROVAL] Step {i+1} image upload completed in {img_upload_time:.3f}s")
|
| 1281 |
+
|
| 1282 |
+
narration_url = tts_future.result()
|
| 1283 |
+
tts_time = time.time() - tts_start
|
| 1284 |
+
logger.info(f"[PROJECT APPROVAL] Step {i+1} TTS generation completed in {tts_time:.3f}s")
|
| 1285 |
+
|
| 1286 |
except Exception as e:
|
| 1287 |
+
logger.error(f"[PROJECT APPROVAL] ERROR processing step {i+1}: {e}")
|
| 1288 |
+
|
| 1289 |
+
result.update({
|
| 1290 |
+
"imageUrl": img_url,
|
| 1291 |
+
"narrationUrl": narration_url,
|
| 1292 |
+
"isDone": False,
|
| 1293 |
+
"notes": ""
|
| 1294 |
+
})
|
| 1295 |
+
return i, result
|
| 1296 |
|
| 1297 |
+
# Process all steps in parallel (up to 5 steps × 2 sub-tasks each)
|
| 1298 |
+
step_args = [(i, parsed_steps[i], inline_images[i]) for i in range(len(parsed_steps))]
|
| 1299 |
+
|
| 1300 |
+
with concurrent.futures.ThreadPoolExecutor(max_workers=len(parsed_steps)) as outer_pool:
|
| 1301 |
+
for i, completed_step in outer_pool.map(process_single_step, step_args):
|
| 1302 |
+
final_steps[i] = completed_step
|
| 1303 |
+
|
| 1304 |
+
step_processing_time = time.time() - step_processing_start
|
| 1305 |
+
logger.info(f"[PROJECT APPROVAL] All steps processing completed in {step_processing_time:.3f}s")
|
| 1306 |
+
|
| 1307 |
+
# Database update timing
|
| 1308 |
+
db_update_start = time.time()
|
| 1309 |
update_data = {
|
| 1310 |
"status": "ready",
|
| 1311 |
"toolsList": tools_list,
|
|
|
|
| 1313 |
"selectedOption": selected_option or ""
|
| 1314 |
}
|
| 1315 |
project_ref.update(update_data)
|
| 1316 |
+
logger.info(f"[PROJECT APPROVAL] Updating data in db: {len(update_data)} fields")
|
| 1317 |
+
db_update_time = time.time() - db_update_start
|
| 1318 |
+
logger.info(f"[PROJECT APPROVAL] Database update completed in {db_update_time:.3f}s")
|
| 1319 |
|
| 1320 |
+
# Final project fetch timing
|
| 1321 |
+
final_fetch_start = time.time()
|
| 1322 |
updated_project = project_ref.get()
|
| 1323 |
updated_project["projectId"] = project_id
|
| 1324 |
+
final_fetch_time = time.time() - final_fetch_start
|
| 1325 |
+
logger.info(f"[PROJECT APPROVAL] Final project fetch completed in {final_fetch_time:.3f}s")
|
| 1326 |
|
| 1327 |
+
# Credits deduction timing
|
| 1328 |
+
credits_update_start = time.time()
|
| 1329 |
+
user_ref.update({'credits': user_data.get('credits', 0) - 5})
|
| 1330 |
+
credits_update_time = time.time() - credits_update_start
|
| 1331 |
+
logger.info(f"[PROJECT APPROVAL] Credits update completed in {credits_update_time:.3f}s")
|
| 1332 |
+
|
| 1333 |
+
# Total time calculation
|
| 1334 |
total_time = time.time() - start_time
|
| 1335 |
+
logger.info(f"[PROJECT APPROVAL] SUCCESS: Project approval completed in {total_time:.3f}s")
|
| 1336 |
+
logger.info(f"[PROJECT APPROVAL] TIMING BREAKDOWN:")
|
| 1337 |
+
logger.info(f"[PROJECT APPROVAL] - Image download: {image_download_time:.3f}s")
|
| 1338 |
+
logger.info(f"[PROJECT APPROVAL] - Image processing: {image_processing_time:.3f}s")
|
| 1339 |
+
logger.info(f"[PROJECT APPROVAL] - Context prep: {context_time:.3f}s")
|
| 1340 |
+
logger.info(f"[PROJECT APPROVAL] - AI generation (Nano Banana 2): {ai_time:.3f}s")
|
| 1341 |
+
logger.info(f"[PROJECT APPROVAL] - Response parsing: {parsing_time:.3f}s")
|
| 1342 |
+
logger.info(f"[PROJECT APPROVAL] - Text extraction: {extraction_time:.3f}s")
|
| 1343 |
+
logger.info(f"[PROJECT APPROVAL] - Step processing (threaded): {step_processing_time:.3f}s")
|
| 1344 |
+
logger.info(f"[PROJECT APPROVAL] - DB update: {db_update_time:.3f}s")
|
| 1345 |
+
logger.info(f"[PROJECT APPROVAL] - Final fetch: {final_fetch_time:.3f}s")
|
| 1346 |
+
logger.info(f"[PROJECT APPROVAL] - Credits update: {credits_update_time:.3f}s")
|
| 1347 |
+
|
| 1348 |
return jsonify(updated_project)
|
| 1349 |
|
| 1350 |
except Exception as e:
|
| 1351 |
+
total_time = time.time() - start_time
|
| 1352 |
+
logger.error(f"[PROJECT APPROVAL] ERROR: Exception occurred after {total_time:.3f}s: {e}")
|
| 1353 |
+
logger.error(f"[PROJECT APPROVAL] Error type: {type(e).__name__}")
|
| 1354 |
+
logger.error(f"[PROJECT APPROVAL] Project ID: {project_id}, User ID: {uid}")
|
| 1355 |
+
logger.error(f"[PROJECT APPROVAL] Full traceback: {traceback.format_exc()}")
|
| 1356 |
return jsonify({'error': f'Internal server error: {str(e)}'}), 500
|
| 1357 |
|
| 1358 |
|