dippoo Claude Opus 4.5 commited on
Commit
4deeec0
·
1 Parent(s): 7634d60

Add API log panel for debugging generation requests

Browse files

- Added collapsible API log panel below preview
- Logs all API calls with method, URL, status code
- Shows error details when requests fail
- Wraps fetch to automatically capture all API activity
- Helps debug cancelled/failed generations

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

Files changed (1) hide show
  1. src/content_engine/api/ui.html +129 -0
src/content_engine/api/ui.html CHANGED
@@ -278,6 +278,44 @@ select { cursor: pointer; }
278
 
279
  .preview-placeholder svg { width: 64px; height: 64px; opacity: 0.3; margin-bottom: 12px; }
280
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
281
  .generating-overlay {
282
  position: absolute;
283
  inset: 0;
@@ -1066,6 +1104,17 @@ select { cursor: pointer; }
1066
  <p style="font-size:12px; margin-top:4px">Write a prompt and click Generate</p>
1067
  </div>
1068
  </div>
 
 
 
 
 
 
 
 
 
 
 
1069
  </div>
1070
  </div>
1071
  </div>
@@ -1468,6 +1517,86 @@ let trainImageFiles = [];
1468
  let trainCaptions = {}; // filename -> caption text
1469
  let selectedTrainBackend = 'local';
1470
  let runpodAvailable = false;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1471
  let galleryImages = [];
1472
  let currentLightboxIndex = -1;
1473
  let galleryOffset = 0;
 
278
 
279
  .preview-placeholder svg { width: 64px; height: 64px; opacity: 0.3; margin-bottom: 12px; }
280
 
281
+ .api-log-panel {
282
+ border-top: 1px solid var(--border);
283
+ background: var(--bg-primary);
284
+ }
285
+ .api-log-header {
286
+ display: flex;
287
+ justify-content: space-between;
288
+ align-items: center;
289
+ padding: 8px 12px;
290
+ cursor: pointer;
291
+ font-size: 12px;
292
+ color: var(--text-secondary);
293
+ user-select: none;
294
+ }
295
+ .api-log-header:hover { background: var(--bg-hover); }
296
+ .api-log-content {
297
+ max-height: 200px;
298
+ overflow-y: auto;
299
+ padding: 8px 12px;
300
+ font-family: 'Consolas', 'Monaco', monospace;
301
+ font-size: 11px;
302
+ }
303
+ .api-log-entry {
304
+ padding: 4px 0;
305
+ border-bottom: 1px solid var(--border);
306
+ line-height: 1.4;
307
+ }
308
+ .api-log-entry:last-child { border-bottom: none; }
309
+ .api-log-time { color: var(--text-secondary); margin-right: 8px; }
310
+ .api-log-method { font-weight: 600; margin-right: 4px; }
311
+ .api-log-method.POST { color: var(--green); }
312
+ .api-log-method.GET { color: var(--blue); }
313
+ .api-log-url { color: var(--text-primary); }
314
+ .api-log-status { margin-left: 8px; font-weight: 600; }
315
+ .api-log-status.ok { color: var(--green); }
316
+ .api-log-status.error { color: var(--red); }
317
+ .api-log-detail { color: var(--text-secondary); margin-top: 2px; padding-left: 60px; word-break: break-all; }
318
+
319
  .generating-overlay {
320
  position: absolute;
321
  inset: 0;
 
1104
  <p style="font-size:12px; margin-top:4px">Write a prompt and click Generate</p>
1105
  </div>
1106
  </div>
1107
+ <!-- API Log Panel -->
1108
+ <div class="api-log-panel">
1109
+ <div class="api-log-header" onclick="toggleApiLog()">
1110
+ <span>API Log</span>
1111
+ <svg id="api-log-chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" style="width:16px;height:16px;transition:transform 0.2s"><polyline points="6 9 12 15 18 9"/></svg>
1112
+ </div>
1113
+ <div id="api-log-content" class="api-log-content" style="display:none">
1114
+ <div id="api-log-entries"></div>
1115
+ <button onclick="clearApiLog()" style="margin-top:8px;padding:4px 8px;font-size:11px;background:var(--bg-hover);border:1px solid var(--border);border-radius:4px;color:var(--text-secondary);cursor:pointer">Clear Log</button>
1116
+ </div>
1117
+ </div>
1118
  </div>
1119
  </div>
1120
  </div>
 
1517
  let trainCaptions = {}; // filename -> caption text
1518
  let selectedTrainBackend = 'local';
1519
  let runpodAvailable = false;
1520
+ let apiLogs = [];
1521
+ const MAX_API_LOGS = 50;
1522
+
1523
+ // --- API Logging ---
1524
+ function logApi(method, url, status, detail = null) {
1525
+ const now = new Date();
1526
+ const time = now.toLocaleTimeString('en-US', { hour12: false });
1527
+ apiLogs.unshift({ time, method, url, status, detail });
1528
+ if (apiLogs.length > MAX_API_LOGS) apiLogs.pop();
1529
+ renderApiLog();
1530
+ }
1531
+
1532
+ function renderApiLog() {
1533
+ const container = document.getElementById('api-log-entries');
1534
+ if (!container) return;
1535
+ container.innerHTML = apiLogs.map(log => `
1536
+ <div class="api-log-entry">
1537
+ <span class="api-log-time">${log.time}</span>
1538
+ <span class="api-log-method ${log.method}">${log.method}</span>
1539
+ <span class="api-log-url">${log.url}</span>
1540
+ <span class="api-log-status ${log.status >= 200 && log.status < 300 ? 'ok' : 'error'}">${log.status}</span>
1541
+ ${log.detail ? `<div class="api-log-detail">${log.detail}</div>` : ''}
1542
+ </div>
1543
+ `).join('');
1544
+ }
1545
+
1546
+ function toggleApiLog() {
1547
+ const content = document.getElementById('api-log-content');
1548
+ const chevron = document.getElementById('api-log-chevron');
1549
+ if (content.style.display === 'none') {
1550
+ content.style.display = '';
1551
+ chevron.style.transform = 'rotate(180deg)';
1552
+ } else {
1553
+ content.style.display = 'none';
1554
+ chevron.style.transform = '';
1555
+ }
1556
+ }
1557
+
1558
+ function clearApiLog() {
1559
+ apiLogs = [];
1560
+ renderApiLog();
1561
+ }
1562
+
1563
+ // Wrap fetch to log API calls
1564
+ const originalFetch = window.fetch;
1565
+ window.fetch = async function(url, options = {}) {
1566
+ const method = options.method || 'GET';
1567
+ const urlStr = typeof url === 'string' ? url : url.toString();
1568
+
1569
+ // Only log API calls, not static resources
1570
+ if (!urlStr.includes('/api/')) {
1571
+ return originalFetch.apply(this, arguments);
1572
+ }
1573
+
1574
+ const shortUrl = urlStr.replace(API, '').split('?')[0];
1575
+
1576
+ try {
1577
+ const response = await originalFetch.apply(this, arguments);
1578
+ const status = response.status;
1579
+
1580
+ // Clone response to read body for error details
1581
+ if (!response.ok) {
1582
+ try {
1583
+ const clone = response.clone();
1584
+ const data = await clone.json();
1585
+ const detail = data.detail || data.error || JSON.stringify(data).substring(0, 100);
1586
+ logApi(method, shortUrl, status, detail);
1587
+ } catch {
1588
+ logApi(method, shortUrl, status, 'Failed to parse error');
1589
+ }
1590
+ } else {
1591
+ logApi(method, shortUrl, status);
1592
+ }
1593
+
1594
+ return response;
1595
+ } catch (error) {
1596
+ logApi(method, shortUrl, 'ERR', error.message);
1597
+ throw error;
1598
+ }
1599
+ };
1600
  let galleryImages = [];
1601
  let currentLightboxIndex = -1;
1602
  let galleryOffset = 0;