Spaces:
Running
Running
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>
- 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;
|