yashgori20 commited on
Commit
c77c822
ยท
1 Parent(s): b02f039
Files changed (1) hide show
  1. app.py +233 -40
app.py CHANGED
@@ -549,6 +549,68 @@ def apply_changes_to_params(parameters, changes):
549
 
550
  return parameters
551
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
552
  def generate_json_template(doc_type, product_name, supplier_name, parameters):
553
  """
554
  JSON template generation with intelligent parameter type handling.
@@ -2813,6 +2875,7 @@ def view_history():
2813
  border: none; border-radius: 6px; margin: 0 10px; text-decoration: none; display: inline-block;
2814
  font-weight: bold; transition: all 0.3s ease; }
2815
  .nav-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3); color: white; }
 
2816
  </style>
2817
  </head>
2818
  <body>
@@ -2941,9 +3004,12 @@ def view_logs():
2941
  l.id, l.request_id, l.endpoint, l.method, l.client_ip,
2942
  l.file_info, l.processing_time_ms, l.status_code,
2943
  l.error_message, l.created_at,
2944
- r.product_name, r.supplier_name, r.doc_type
 
 
2945
  FROM api_logs l
2946
  LEFT JOIN qc_requests r ON l.request_id = r.id
 
2947
  WHERE 1=1
2948
  """
2949
  params = []
@@ -2978,7 +3044,9 @@ def view_logs():
2978
  "created_at": row[9],
2979
  "product_name": row[10],
2980
  "supplier_name": row[11],
2981
- "doc_type": row[12]
 
 
2982
  })
2983
 
2984
  return jsonify({
@@ -3002,16 +3070,19 @@ def view_logs():
3002
 
3003
  # Get logs with request details
3004
  cur.execute("""
3005
- SELECT
3006
- l.id, l.request_id, l.endpoint, l.method, l.client_ip,
3007
- l.file_info, l.processing_time_ms, l.status_code,
3008
- l.error_message, l.created_at,
3009
- r.product_name, r.supplier_name, r.doc_type
3010
- FROM api_logs l
3011
- LEFT JOIN qc_requests r ON l.request_id = r.id
3012
- ORDER BY l.created_at DESC
3013
- LIMIT 100
3014
- """)
 
 
 
3015
 
3016
  rows = cur.fetchall()
3017
 
@@ -3136,7 +3207,7 @@ def view_logs():
3136
  <th>File Info</th>
3137
  <th>Time (ms)</th>
3138
  <th>Status</th>
3139
- <th>Client IP</th>
3140
  <th>Error</th>
3141
  <th>Created At</th>
3142
  <th>Actions</th>
@@ -3144,7 +3215,7 @@ def view_logs():
3144
  """
3145
 
3146
  for row in rows:
3147
- log_id, request_id, endpoint, method, client_ip, file_info, processing_time, status_code, error_message, created_at, product_name, supplier_name, doc_type = row
3148
 
3149
  # Style endpoint
3150
  endpoint_class = endpoint.replace('/', '').lower()
@@ -3169,9 +3240,12 @@ def view_logs():
3169
  # Format error message
3170
  error_display = f'<span class="error-msg" title="{error_message or ""}">{(error_message or "")[:50]}{"..." if error_message and len(error_message) > 50 else ""}</span>'
3171
 
 
 
 
3172
  html += f"""
3173
  <tr class="log-row" data-endpoint="{endpoint}" data-status="{status_code}">
3174
- <td>{log_id}</td>
3175
  <td>{request_id or "N/A"}</td>
3176
  <td>{endpoint_badge}</td>
3177
  <td><strong>{product_name or "N/A"}</strong></td>
@@ -3179,11 +3253,12 @@ def view_logs():
3179
  <td>{file_display}</td>
3180
  <td>{time_text}</td>
3181
  <td>{status_text}</td>
3182
- <td>{client_ip}</td>
3183
  <td>{error_display}</td>
3184
  <td>{created_at}</td>
3185
  <td>
3186
- {f'<a href="/preview/{request_id}">Preview</a> <a href="/template/{request_id}">JSON</a>' if request_id else 'N/A'}
 
3187
  </td>
3188
  </tr>
3189
  """
@@ -3205,7 +3280,6 @@ def view_logs():
3205
  return f"<h1>Error</h1><p>{str(e)}</p>", 500
3206
 
3207
  # ADD this new endpoint after the /logs endpoint
3208
-
3209
  @app.route("/logs/<int:log_id>", methods=["GET"])
3210
  def view_detailed_log(log_id):
3211
  """View detailed information for a specific log entry"""
@@ -3251,10 +3325,11 @@ def view_detailed_log(log_id):
3251
  "product_name": log_data[14],
3252
  "supplier_name": log_data[15],
3253
  "user_message": log_data[16],
 
3254
  "llm_response_summary": log_data[18]
3255
  })
3256
 
3257
- # HTML detailed view
3258
  html = f"""
3259
  <html>
3260
  <head>
@@ -3267,6 +3342,13 @@ def view_detailed_log(log_id):
3267
  .request-info {{ background: #f8f9fa; border-left: 4px solid #28a745; }}
3268
  .response-info {{ background: #fff3cd; border-left: 4px solid #ffc107; }}
3269
  .error-info {{ background: #f8d7da; border-left: 4px solid #dc3545; }}
 
 
 
 
 
 
 
3270
  h1, h2 {{ color: #333; }}
3271
  h1 {{ text-align: center; }}
3272
  pre {{ background: #f8f9fa; padding: 15px; border-radius: 5px; overflow: auto; max-height: 300px; }}
@@ -3275,6 +3357,8 @@ def view_detailed_log(log_id):
3275
  .nav-btn {{ background: #007bff; color: white; padding: 10px 15px; text-decoration: none; border-radius: 5px; margin: 5px; display: inline-block; }}
3276
  .nav-btn:hover {{ background: #0056b3; color: white; }}
3277
  .metric {{ display: inline-block; margin: 10px 15px 10px 0; padding: 8px 12px; background: white; border-radius: 5px; border: 1px solid #ddd; }}
 
 
3278
  </style>
3279
  </head>
3280
  <body>
@@ -3296,39 +3380,146 @@ def view_detailed_log(log_id):
3296
  <div class="metric"><strong>Created:</strong> {log_data[12]}</div>
3297
  <div class="metric"><strong>Client IP:</strong> {log_data[4]}</div>
3298
  </div>
3299
-
3300
- {f'''
 
 
 
3301
  <div class="section request-info">
3302
  <h2>๐Ÿ“‹ Request Information</h2>
3303
- <p><strong>Product:</strong> {log_data[14]}</p>
3304
- <p><strong>Document Type:</strong> {log_data[13]}</p>
3305
- <p><strong>Supplier:</strong> {log_data[15]}</p>
3306
- {f'<p><strong>File Info:</strong> {log_data[8]}</p>' if log_data[8] else ''}
3307
- {f'<div><strong>User Message:</strong><pre>{log_data[16][:500]}{"..." if log_data[16] and len(log_data[16]) > 500 else ""}</pre></div>' if log_data[16] else ''}
3308
- </div>
3309
- ''' if log_data[1] else ''}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3310
 
3311
- {f'''
3312
- <div class="section response-info">
3313
- <h2>๐Ÿ“ค Response Information</h2>
3314
- {f'<div><strong>Response Data:</strong><pre>{log_data[7]}</pre></div>' if log_data[7] else '<p>No response data available</p>'}
3315
- </div>
3316
- ''' if log_data[7] else ''}
 
 
 
 
 
 
 
 
 
 
 
3317
 
3318
- {f'''
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3319
  <div class="section error-info">
3320
  <h2>โŒ Error Information</h2>
3321
  <p><strong>Error Message:</strong></p>
3322
  <pre>{log_data[11]}</pre>
3323
  </div>
3324
- ''' if log_data[11] else ''}
3325
-
 
 
3326
  <div class="section">
3327
  <h2>๐ŸŒ Technical Details</h2>
3328
  <p><strong>User Agent:</strong> {log_data[5] or 'Not available'}</p>
3329
- {f'<div><strong>Request Data:</strong><pre>{log_data[6]}</pre></div>' if log_data[6] else '<p>No request data captured</p>'}
3330
- </div>
3331
- </div>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3332
  </body>
3333
  </html>
3334
  """
@@ -3337,6 +3528,8 @@ def view_detailed_log(log_id):
3337
 
3338
  except Exception as e:
3339
  return f"<h1>Error</h1><p>{str(e)}</p>", 500
 
 
3340
  @app.route("/preview/<int:request_id>", methods=["GET"])
3341
  def preview_page(request_id):
3342
  """preview with better formatting and metadata"""
 
549
 
550
  return parameters
551
 
552
+ def _analyze_llm_response(llm_response):
553
+ """Analyze LLM response to extract key information"""
554
+ if not llm_response:
555
+ return ""
556
+
557
+ analysis_html = '<div class="json-analysis"><h4>๐Ÿ“Š Response Analysis</h4>'
558
+
559
+ # Try to extract JSON array
560
+ import re
561
+ json_match = re.search(r'\[.*?\]', llm_response, re.DOTALL)
562
+
563
+ if json_match:
564
+ try:
565
+ import json
566
+ json_content = json_match.group(0)
567
+ parsed_json = json.loads(json_content)
568
+
569
+ if isinstance(parsed_json, list):
570
+ param_count = len(parsed_json)
571
+ analysis_html += f'<p><strong>Parameters Generated:</strong> <span class="parameter-count">{param_count}</span></p>'
572
+
573
+ # Analyze parameter types
574
+ param_types = {}
575
+ for param in parsed_json:
576
+ if isinstance(param, dict):
577
+ param_type = param.get('Type', 'Unknown')
578
+ param_types[param_type] = param_types.get(param_type, 0) + 1
579
+
580
+ if param_types:
581
+ analysis_html += '<p><strong>Parameter Types:</strong></p><ul>'
582
+ for ptype, count in param_types.items():
583
+ analysis_html += f'<li>{ptype}: {count}</li>'
584
+ analysis_html += '</ul>'
585
+
586
+ # Show first few parameter names
587
+ param_names = [p.get('Parameter', 'Unnamed') for p in parsed_json[:5] if isinstance(p, dict)]
588
+ if param_names:
589
+ analysis_html += f'<p><strong>Sample Parameters:</strong> {", ".join(param_names)}</p>'
590
+ if len(parsed_json) > 5:
591
+ analysis_html += f'<p><em>...and {len(parsed_json) - 5} more parameters</em></p>'
592
+
593
+ except json.JSONDecodeError:
594
+ analysis_html += '<p><span style="color: #ffc107;">โš ๏ธ JSON found but could not parse completely</span></p>'
595
+ else:
596
+ analysis_html += '<p><span style="color: #dc3545;">โŒ No JSON array found in response</span></p>'
597
+
598
+ # Check response length
599
+ response_length = len(llm_response)
600
+ if response_length > 10000:
601
+ analysis_html += f'<p><strong>Response Size:</strong> <span style="color: #ffc107;">{response_length:,} characters (Large response)</span></p>'
602
+ else:
603
+ analysis_html += f'<p><strong>Response Size:</strong> {response_length:,} characters</p>'
604
+
605
+ # Check for errors or issues
606
+ if 'error' in llm_response.lower() or 'failed' in llm_response.lower():
607
+ analysis_html += '<p><span style="color: #dc3545;">โš ๏ธ Response may contain error indicators</span></p>'
608
+
609
+ analysis_html += '</div>'
610
+ return analysis_html
611
+
612
+
613
+
614
  def generate_json_template(doc_type, product_name, supplier_name, parameters):
615
  """
616
  JSON template generation with intelligent parameter type handling.
 
2875
  border: none; border-radius: 6px; margin: 0 10px; text-decoration: none; display: inline-block;
2876
  font-weight: bold; transition: all 0.3s ease; }
2877
  .nav-btn:hover { transform: translateY(-2px); box-shadow: 0 4px 12px rgba(0, 123, 255, 0.3); color: white; }
2878
+
2879
  </style>
2880
  </head>
2881
  <body>
 
3004
  l.id, l.request_id, l.endpoint, l.method, l.client_ip,
3005
  l.file_info, l.processing_time_ms, l.status_code,
3006
  l.error_message, l.created_at,
3007
+ r.product_name, r.supplier_name, r.doc_type,
3008
+ lr.summary_text,
3009
+ CASE WHEN lr.llm_response IS NOT NULL THEN 'Yes' ELSE 'No' END as has_llm_response
3010
  FROM api_logs l
3011
  LEFT JOIN qc_requests r ON l.request_id = r.id
3012
+ LEFT JOIN llm_responses lr ON l.request_id = lr.request_id
3013
  WHERE 1=1
3014
  """
3015
  params = []
 
3044
  "created_at": row[9],
3045
  "product_name": row[10],
3046
  "supplier_name": row[11],
3047
+ "doc_type": row[12],
3048
+ "llm_summary": row[13],
3049
+ "has_llm_response": row[14]
3050
  })
3051
 
3052
  return jsonify({
 
3070
 
3071
  # Get logs with request details
3072
  cur.execute("""
3073
+ SELECT
3074
+ l.id, l.request_id, l.endpoint, l.method, l.client_ip,
3075
+ l.file_info, l.processing_time_ms, l.status_code,
3076
+ l.error_message, l.created_at,
3077
+ r.product_name, r.supplier_name, r.doc_type,
3078
+ lr.summary_text,
3079
+ CASE WHEN lr.llm_response IS NOT NULL THEN 'Yes' ELSE 'No' END as has_llm_response
3080
+ FROM api_logs l
3081
+ LEFT JOIN qc_requests r ON l.request_id = r.id
3082
+ LEFT JOIN llm_responses lr ON l.request_id = lr.request_id
3083
+ ORDER BY l.created_at DESC
3084
+ LIMIT 100
3085
+ """)
3086
 
3087
  rows = cur.fetchall()
3088
 
 
3207
  <th>File Info</th>
3208
  <th>Time (ms)</th>
3209
  <th>Status</th>
3210
+ <th>LLM Used</th>
3211
  <th>Error</th>
3212
  <th>Created At</th>
3213
  <th>Actions</th>
 
3215
  """
3216
 
3217
  for row in rows:
3218
+ log_id, request_id, endpoint, method, client_ip, file_info, processing_time, status_code, error_message, created_at, product_name, supplier_name, doc_type, llm_summary, has_llm_response = row
3219
 
3220
  # Style endpoint
3221
  endpoint_class = endpoint.replace('/', '').lower()
 
3240
  # Format error message
3241
  error_display = f'<span class="error-msg" title="{error_message or ""}">{(error_message or "")[:50]}{"..." if error_message and len(error_message) > 50 else ""}</span>'
3242
 
3243
+ llm_indicator = "๐Ÿค– Yes" if has_llm_response == "Yes" else "โŒ No"
3244
+ llm_color = "#28a745" if has_llm_response == "Yes" else "#6c757d"
3245
+
3246
  html += f"""
3247
  <tr class="log-row" data-endpoint="{endpoint}" data-status="{status_code}">
3248
+ <td><a href="/logs/{log_id}" style="color: #007bff; font-weight: bold;">#{log_id}</a></td>
3249
  <td>{request_id or "N/A"}</td>
3250
  <td>{endpoint_badge}</td>
3251
  <td><strong>{product_name or "N/A"}</strong></td>
 
3253
  <td>{file_display}</td>
3254
  <td>{time_text}</td>
3255
  <td>{status_text}</td>
3256
+ <td><span style="color: {llm_color}; font-weight: bold;">{llm_indicator}</span></td>
3257
  <td>{error_display}</td>
3258
  <td>{created_at}</td>
3259
  <td>
3260
+ <a href="/logs/{log_id}">Details</a>
3261
+ {f'<a href="/preview/{request_id}">Preview</a> <a href="/template/{request_id}">JSON</a>' if request_id else ''}
3262
  </td>
3263
  </tr>
3264
  """
 
3280
  return f"<h1>Error</h1><p>{str(e)}</p>", 500
3281
 
3282
  # ADD this new endpoint after the /logs endpoint
 
3283
  @app.route("/logs/<int:log_id>", methods=["GET"])
3284
  def view_detailed_log(log_id):
3285
  """View detailed information for a specific log entry"""
 
3325
  "product_name": log_data[14],
3326
  "supplier_name": log_data[15],
3327
  "user_message": log_data[16],
3328
+ "llm_response": log_data[17],
3329
  "llm_response_summary": log_data[18]
3330
  })
3331
 
3332
+ # Start building HTML
3333
  html = f"""
3334
  <html>
3335
  <head>
 
3342
  .request-info {{ background: #f8f9fa; border-left: 4px solid #28a745; }}
3343
  .response-info {{ background: #fff3cd; border-left: 4px solid #ffc107; }}
3344
  .error-info {{ background: #f8d7da; border-left: 4px solid #dc3545; }}
3345
+ .llm-section {{ background: #f0f8ff; border-left: 4px solid #007bff; }}
3346
+ .json-analysis {{ background: #f8f9fa; border: 1px solid #dee2e6; border-radius: 5px; padding: 10px; margin: 10px 0; }}
3347
+ .json-preview {{ background: #2d3748; color: #e2e8f0; padding: 15px; border-radius: 5px; font-family: 'Courier New', monospace; font-size: 12px; }}
3348
+ .expandable {{ cursor: pointer; user-select: none; }}
3349
+ .expandable:hover {{ background: #e9ecef; }}
3350
+ .collapsed {{ display: none; }}
3351
+ .parameter-count {{ background: #28a745; color: white; padding: 2px 8px; border-radius: 12px; font-size: 11px; margin-left: 10px; }}
3352
  h1, h2 {{ color: #333; }}
3353
  h1 {{ text-align: center; }}
3354
  pre {{ background: #f8f9fa; padding: 15px; border-radius: 5px; overflow: auto; max-height: 300px; }}
 
3357
  .nav-btn {{ background: #007bff; color: white; padding: 10px 15px; text-decoration: none; border-radius: 5px; margin: 5px; display: inline-block; }}
3358
  .nav-btn:hover {{ background: #0056b3; color: white; }}
3359
  .metric {{ display: inline-block; margin: 10px 15px 10px 0; padding: 8px 12px; background: white; border-radius: 5px; border: 1px solid #ddd; }}
3360
+ .copy-btn {{ background: #17a2b8; color: white; border: none; padding: 5px 10px; border-radius: 3px; cursor: pointer; float: right; margin: 5px; }}
3361
+ .copy-btn:hover {{ background: #138496; }}
3362
  </style>
3363
  </head>
3364
  <body>
 
3380
  <div class="metric"><strong>Created:</strong> {log_data[12]}</div>
3381
  <div class="metric"><strong>Client IP:</strong> {log_data[4]}</div>
3382
  </div>
3383
+ """
3384
+
3385
+ # Add request information section if we have request data
3386
+ if log_data[1]: # If we have a request_id
3387
+ html += f"""
3388
  <div class="section request-info">
3389
  <h2>๐Ÿ“‹ Request Information</h2>
3390
+ <p><strong>Product:</strong> {log_data[14] or 'N/A'}</p>
3391
+ <p><strong>Document Type:</strong> {log_data[13] or 'N/A'}</p>
3392
+ <p><strong>Supplier:</strong> {log_data[15] or 'N/A'}</p>
3393
+ {f'<p><strong>File Info:</strong> {log_data[8]}</p>' if log_data[8] else '<p><strong>File Info:</strong> No file uploaded</p>'}
3394
+ """
3395
+
3396
+ if log_data[16]: # User message
3397
+ user_message_preview = log_data[16][:500]
3398
+ if len(log_data[16]) > 500:
3399
+ user_message_preview += "..."
3400
+ html += f"""
3401
+ <div><strong>User Message:</strong>
3402
+ <pre>{user_message_preview}</pre></div>
3403
+ """
3404
+
3405
+ html += "</div>"
3406
+
3407
+ # Add LLM Processing section if we have LLM data
3408
+ if log_data[17] or log_data[7]:
3409
+ html += f"""
3410
+ <div class="section llm-section">
3411
+ <h2>๐Ÿค– LLM Processing Chain</h2>
3412
+ <p><strong>Summary:</strong> {log_data[18] or 'No summary available'}</p>
3413
+ """
3414
+
3415
+ # Raw LLM Response section
3416
+ if log_data[17]:
3417
+ # Escape the LLM response for safe JavaScript usage
3418
+ llm_response_js_safe = str(log_data[17]).replace("\\", "\\\\").replace("'", "\\'").replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r')
3419
 
3420
+ html += f"""
3421
+ <div class="json-analysis">
3422
+ <h3 class="expandable" onclick="toggleSection('llm-response-section')">โ–ผ Raw LLM Response</h3>
3423
+ <div id="llm-response-section">
3424
+ <button class="copy-btn" onclick="copyToClipboard('{llm_response_js_safe}')">๐Ÿ“‹ Copy</button>
3425
+ <pre id="llm-response-content" class="json-preview" style="max-height: 500px; overflow-y: auto;">{log_data[17]}</pre>
3426
+ </div>
3427
+ </div>
3428
+ """
3429
+
3430
+ # Add LLM response analysis
3431
+ html += _analyze_llm_response(log_data[17])
3432
+
3433
+ # Final API Response section
3434
+ if log_data[7]:
3435
+ # Escape the final response for safe JavaScript usage
3436
+ final_response_js_safe = str(log_data[7]).replace("\\", "\\\\").replace("'", "\\'").replace('"', '\\"').replace('\n', '\\n').replace('\r', '\\r')
3437
 
3438
+ html += f"""
3439
+ <div class="json-analysis">
3440
+ <h3 class="expandable" onclick="toggleSection('final-response-section')">โ–ผ Final API Response</h3>
3441
+ <div id="final-response-section">
3442
+ <button class="copy-btn" onclick="copyToClipboard('{final_response_js_safe}')">๐Ÿ“‹ Copy</button>
3443
+ <pre class="json-preview">{log_data[7]}</pre>
3444
+ </div>
3445
+ </div>
3446
+ """
3447
+
3448
+ html += "</div>"
3449
+
3450
+ # Add error information if present
3451
+ if log_data[11]:
3452
+ html += f"""
3453
  <div class="section error-info">
3454
  <h2>โŒ Error Information</h2>
3455
  <p><strong>Error Message:</strong></p>
3456
  <pre>{log_data[11]}</pre>
3457
  </div>
3458
+ """
3459
+
3460
+ # Add technical details section
3461
+ html += f"""
3462
  <div class="section">
3463
  <h2>๐ŸŒ Technical Details</h2>
3464
  <p><strong>User Agent:</strong> {log_data[5] or 'Not available'}</p>
3465
+ """
3466
+
3467
+ if log_data[6]:
3468
+ html += f"""
3469
+ <div><strong>Request Data:</strong>
3470
+ <pre>{log_data[6]}</pre></div>
3471
+ """
3472
+ else:
3473
+ html += "<p>No request data captured</p>"
3474
+
3475
+ html += "</div></div>"
3476
+
3477
+ # Add JavaScript section safely
3478
+ html += """
3479
+ <script>
3480
+ function toggleSection(id) {
3481
+ const element = document.getElementById(id);
3482
+ const button = document.querySelector('[onclick*="' + id + '"]');
3483
+
3484
+ if (element && button) {
3485
+ if (element.classList.contains('collapsed')) {
3486
+ element.classList.remove('collapsed');
3487
+ button.textContent = button.textContent.replace('โ–ถ', 'โ–ผ');
3488
+ } else {
3489
+ element.classList.add('collapsed');
3490
+ button.textContent = button.textContent.replace('โ–ผ', 'โ–ถ');
3491
+ }
3492
+ }
3493
+ }
3494
+
3495
+ function copyToClipboard(text) {
3496
+ // Create a temporary textarea element
3497
+ const textArea = document.createElement('textarea');
3498
+ textArea.value = text;
3499
+ textArea.style.position = 'fixed';
3500
+ textArea.style.opacity = '0';
3501
+ document.body.appendChild(textArea);
3502
+ textArea.select();
3503
+
3504
+ try {
3505
+ document.execCommand('copy');
3506
+ alert('โœ… Copied to clipboard!');
3507
+ } catch (err) {
3508
+ console.error('Failed to copy: ', err);
3509
+ alert('โŒ Failed to copy to clipboard');
3510
+ }
3511
+
3512
+ document.body.removeChild(textArea);
3513
+ }
3514
+
3515
+ // Auto-expand LLM response if it contains JSON
3516
+ document.addEventListener('DOMContentLoaded', function() {
3517
+ const llmResponse = document.getElementById('llm-response-content');
3518
+ if (llmResponse && llmResponse.textContent.includes('[')) {
3519
+ // Likely contains JSON, keep expanded by default
3520
+ }
3521
+ });
3522
+ </script>
3523
  </body>
3524
  </html>
3525
  """
 
3528
 
3529
  except Exception as e:
3530
  return f"<h1>Error</h1><p>{str(e)}</p>", 500
3531
+
3532
+
3533
  @app.route("/preview/<int:request_id>", methods=["GET"])
3534
  def preview_page(request_id):
3535
  """preview with better formatting and metadata"""