@dynamicu/chromedebug-mcp 2.6.6 → 2.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/chrome-extension/activation-manager.js +18 -4
- package/chrome-extension/background.js +439 -53
- package/chrome-extension/browser-recording-manager.js +256 -0
- package/chrome-extension/content.js +167 -97
- package/chrome-extension/data-buffer.js +206 -17
- package/chrome-extension/frame-capture.js +52 -15
- package/chrome-extension/manifest.free.json +3 -3
- package/chrome-extension/popup.html +109 -5
- package/chrome-extension/popup.js +597 -159
- package/config/chromedebug-config.json +101 -0
- package/dist/chromedebug-extension-free.zip +0 -0
- package/package.json +1 -1
- package/scripts/package-pro-extension.js +1 -1
- package/scripts/webpack.config.free.cjs +3 -0
- package/scripts/webpack.config.pro.cjs +3 -0
- package/src/chrome-controller.js +7 -7
- package/src/database.js +6 -2
- package/src/http-server.js +3 -2
- package/src/validation/schemas.js +36 -6
|
@@ -46,6 +46,69 @@ let workflowStartTime = null;
|
|
|
46
46
|
// Debounce timer for screenshot quality slider
|
|
47
47
|
let screenshotQualityDebounceTimer = null;
|
|
48
48
|
|
|
49
|
+
// Toast notification function
|
|
50
|
+
function showToast(message, duration = 2000, type = 'info') {
|
|
51
|
+
// Create toast element
|
|
52
|
+
const toast = document.createElement('div');
|
|
53
|
+
toast.className = `toast toast-${type}`;
|
|
54
|
+
toast.textContent = message;
|
|
55
|
+
toast.style.cssText = `
|
|
56
|
+
position: fixed;
|
|
57
|
+
top: 20px;
|
|
58
|
+
right: 20px;
|
|
59
|
+
padding: 12px 20px;
|
|
60
|
+
background: ${type === 'error' ? '#f44336' : type === 'success' ? '#4CAF50' : '#2196F3'};
|
|
61
|
+
color: white;
|
|
62
|
+
border-radius: 4px;
|
|
63
|
+
box-shadow: 0 2px 5px rgba(0,0,0,0.3);
|
|
64
|
+
z-index: 10000;
|
|
65
|
+
font-size: 14px;
|
|
66
|
+
animation: slideInRight 0.3s ease-out;
|
|
67
|
+
`;
|
|
68
|
+
|
|
69
|
+
document.body.appendChild(toast);
|
|
70
|
+
|
|
71
|
+
// Auto remove after duration
|
|
72
|
+
setTimeout(() => {
|
|
73
|
+
toast.style.animation = 'slideOutRight 0.3s ease-in';
|
|
74
|
+
setTimeout(() => toast.remove(), 300);
|
|
75
|
+
}, duration);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Loading overlay helper functions
|
|
79
|
+
function showSectionLoading(sectionId, message = 'Loading...') {
|
|
80
|
+
console.log(`showSectionLoading called for: ${sectionId} with message: ${message}`);
|
|
81
|
+
const section = document.getElementById(sectionId);
|
|
82
|
+
if (!section) {
|
|
83
|
+
console.error(`Section not found: ${sectionId}`);
|
|
84
|
+
return;
|
|
85
|
+
}
|
|
86
|
+
console.log(`Section found:`, section);
|
|
87
|
+
|
|
88
|
+
// Remove any existing overlay first
|
|
89
|
+
hideSectionLoading(sectionId);
|
|
90
|
+
|
|
91
|
+
const overlay = document.createElement('div');
|
|
92
|
+
overlay.className = 'recording-section-overlay';
|
|
93
|
+
overlay.innerHTML = `
|
|
94
|
+
<div class="spinner"></div>
|
|
95
|
+
<div class="loading-text">${message}</div>
|
|
96
|
+
`;
|
|
97
|
+
overlay.setAttribute('data-loading-overlay', 'true');
|
|
98
|
+
section.appendChild(overlay);
|
|
99
|
+
console.log(`Overlay appended to section. Overlay:`, overlay);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
function hideSectionLoading(sectionId) {
|
|
103
|
+
const section = document.getElementById(sectionId);
|
|
104
|
+
if (!section) return;
|
|
105
|
+
|
|
106
|
+
const overlay = section.querySelector('[data-loading-overlay="true"]');
|
|
107
|
+
if (overlay) {
|
|
108
|
+
overlay.remove();
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
49
112
|
// Workflow recording functions
|
|
50
113
|
function updateWorkflowRecordingUI() {
|
|
51
114
|
const workflowBtn = document.getElementById('workflowRecordBtn');
|
|
@@ -319,10 +382,13 @@ async function checkServerStatus() {
|
|
|
319
382
|
});
|
|
320
383
|
|
|
321
384
|
clearTimeout(timeoutId);
|
|
322
|
-
|
|
385
|
+
// Only log successful connections to reduce console noise
|
|
386
|
+
if (response.ok) {
|
|
387
|
+
console.log(`[popup.js] ✓ Connected to server on port ${port}`);
|
|
388
|
+
}
|
|
323
389
|
return response.ok;
|
|
324
390
|
} catch (error) {
|
|
325
|
-
|
|
391
|
+
// Silent failure - port discovery is expected to fail for most ports
|
|
326
392
|
return false;
|
|
327
393
|
}
|
|
328
394
|
};
|
|
@@ -333,7 +399,6 @@ async function checkServerStatus() {
|
|
|
333
399
|
if (await tryPort(cachedPort.lastSuccessfulPort, 500)) {
|
|
334
400
|
connected = true;
|
|
335
401
|
connectedPort = cachedPort.lastSuccessfulPort;
|
|
336
|
-
console.log(`[popup.js] Connected to cached port ${connectedPort}`);
|
|
337
402
|
}
|
|
338
403
|
}
|
|
339
404
|
|
|
@@ -344,7 +409,6 @@ async function checkServerStatus() {
|
|
|
344
409
|
if (await tryPort(port, 500)) {
|
|
345
410
|
connected = true;
|
|
346
411
|
connectedPort = port;
|
|
347
|
-
console.log(`[popup.js] Connected to common port ${connectedPort}`);
|
|
348
412
|
break;
|
|
349
413
|
}
|
|
350
414
|
}
|
|
@@ -356,7 +420,6 @@ async function checkServerStatus() {
|
|
|
356
420
|
if (await tryPort(port, 1000)) {
|
|
357
421
|
connected = true;
|
|
358
422
|
connectedPort = port;
|
|
359
|
-
console.log(`[popup.js] Connected to extended port ${connectedPort}`);
|
|
360
423
|
break;
|
|
361
424
|
}
|
|
362
425
|
}
|
|
@@ -368,7 +431,6 @@ async function checkServerStatus() {
|
|
|
368
431
|
if (await tryPort(port, 1000)) {
|
|
369
432
|
connected = true;
|
|
370
433
|
connectedPort = port;
|
|
371
|
-
console.log(`[popup.js] Connected to alternative port ${connectedPort}`);
|
|
372
434
|
break;
|
|
373
435
|
}
|
|
374
436
|
}
|
|
@@ -377,6 +439,8 @@ async function checkServerStatus() {
|
|
|
377
439
|
// Cache the successful port for faster connection next time
|
|
378
440
|
if (connected && connectedPort) {
|
|
379
441
|
await chrome.storage.local.set({ lastSuccessfulPort: connectedPort });
|
|
442
|
+
} else {
|
|
443
|
+
console.log('[popup.js] No server found - browser-only mode active');
|
|
380
444
|
}
|
|
381
445
|
|
|
382
446
|
if (connected) {
|
|
@@ -902,7 +966,16 @@ function updateRecordingsDisplay(recordings) {
|
|
|
902
966
|
const recordingId = e.target.getAttribute('data-id');
|
|
903
967
|
const type = e.target.getAttribute('data-type');
|
|
904
968
|
|
|
905
|
-
//
|
|
969
|
+
// First check if server is available (for server recordings)
|
|
970
|
+
const serverStatus = await checkServerStatus();
|
|
971
|
+
|
|
972
|
+
if (!serverStatus.connected) {
|
|
973
|
+
// No server - this is a server recording that requires server to view
|
|
974
|
+
alert('This recording requires Chrome Debug server to view.\n\nStart the server with: chromedebug-mcp-server\n\nOr delete this old recording and create new browser-only recordings with Export functionality.');
|
|
975
|
+
return;
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
// Server available - check if pro version (frame editor) is available
|
|
906
979
|
try {
|
|
907
980
|
const frameEditorUrl = type === 'snapshot'
|
|
908
981
|
? chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${recordingId}&type=snapshot`)
|
|
@@ -1016,8 +1089,9 @@ async function showInlineActivationManager(licenseKey) {
|
|
|
1016
1089
|
activationsList.innerHTML = '<div style="padding: 15px; text-align: center; color: #666;">Loading activations...</div>';
|
|
1017
1090
|
|
|
1018
1091
|
// Get current instance ID
|
|
1019
|
-
|
|
1020
|
-
const
|
|
1092
|
+
// Note: ls_instance_id is the LemonSqueezy instance ID set during activation
|
|
1093
|
+
const stored = await chrome.storage.local.get(['ls_instance_id']);
|
|
1094
|
+
const currentInstanceId = stored.ls_instance_id;
|
|
1021
1095
|
|
|
1022
1096
|
// Fetch activations from Firebase
|
|
1023
1097
|
const data = await licenseClient.listActivations(licenseKey);
|
|
@@ -1063,7 +1137,8 @@ function renderInlineActivations(activations, currentInstanceId, licenseKey) {
|
|
|
1063
1137
|
item.onmouseleave = () => item.style.background = isCurrentDevice ? '#e8f5e9' : 'white';
|
|
1064
1138
|
|
|
1065
1139
|
// Check if this is the current device
|
|
1066
|
-
|
|
1140
|
+
// Compare against identifier (unique per activation), not name (same for all activations from this device)
|
|
1141
|
+
const isCurrentDevice = currentInstanceId && activation.identifier === currentInstanceId;
|
|
1067
1142
|
|
|
1068
1143
|
if (isCurrentDevice) {
|
|
1069
1144
|
item.style.background = '#e8f5e9';
|
|
@@ -1282,18 +1357,37 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1282
1357
|
const serverStatus = await checkServerStatus();
|
|
1283
1358
|
const recordBtn = document.getElementById('recordBtn');
|
|
1284
1359
|
const workflowRecordBtn = document.getElementById('workflowRecordBtn');
|
|
1285
|
-
|
|
1360
|
+
const browserOnlyBanner = document.getElementById('browserOnlyBanner');
|
|
1361
|
+
const quotaMeter = document.getElementById('quotaMeter');
|
|
1362
|
+
|
|
1286
1363
|
if (!serverStatus.connected) {
|
|
1287
|
-
//
|
|
1288
|
-
if (
|
|
1289
|
-
|
|
1290
|
-
|
|
1364
|
+
// Show browser-only mode UI
|
|
1365
|
+
if (browserOnlyBanner) {
|
|
1366
|
+
browserOnlyBanner.style.display = 'block';
|
|
1367
|
+
}
|
|
1368
|
+
if (quotaMeter) {
|
|
1369
|
+
quotaMeter.style.display = 'block';
|
|
1370
|
+
updateQuotaUI(); // Update quota display
|
|
1371
|
+
}
|
|
1372
|
+
|
|
1373
|
+
// Enable recording buttons in browser-only mode
|
|
1374
|
+
if (recordBtn) {
|
|
1375
|
+
recordBtn.style.opacity = '1';
|
|
1376
|
+
recordBtn.title = 'Recording will be stored in browser storage';
|
|
1291
1377
|
}
|
|
1292
|
-
if (workflowRecordBtn
|
|
1293
|
-
workflowRecordBtn.style.opacity = '
|
|
1294
|
-
workflowRecordBtn.title = '
|
|
1378
|
+
if (workflowRecordBtn) {
|
|
1379
|
+
workflowRecordBtn.style.opacity = '1';
|
|
1380
|
+
workflowRecordBtn.title = 'Recording will be stored in browser storage';
|
|
1295
1381
|
}
|
|
1296
1382
|
} else {
|
|
1383
|
+
// Hide browser-only mode UI when server is connected
|
|
1384
|
+
if (browserOnlyBanner) {
|
|
1385
|
+
browserOnlyBanner.style.display = 'none';
|
|
1386
|
+
}
|
|
1387
|
+
if (quotaMeter) {
|
|
1388
|
+
quotaMeter.style.display = 'none';
|
|
1389
|
+
}
|
|
1390
|
+
|
|
1297
1391
|
// Enable recording buttons when server is running
|
|
1298
1392
|
if (recordBtn) {
|
|
1299
1393
|
recordBtn.style.opacity = '1';
|
|
@@ -1305,11 +1399,127 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1305
1399
|
}
|
|
1306
1400
|
}
|
|
1307
1401
|
}
|
|
1402
|
+
|
|
1403
|
+
// Update quota UI for browser-only mode
|
|
1404
|
+
async function updateQuotaUI() {
|
|
1405
|
+
try {
|
|
1406
|
+
// Request quota info from background script
|
|
1407
|
+
const response = await chrome.runtime.sendMessage({
|
|
1408
|
+
action: 'getQuotaInfo'
|
|
1409
|
+
});
|
|
1410
|
+
|
|
1411
|
+
if (response && response.quotaInfo) {
|
|
1412
|
+
const { percentage, usageMB, quotaMB, availableMB } = response.quotaInfo;
|
|
1413
|
+
|
|
1414
|
+
const quotaBar = document.getElementById('quotaBar');
|
|
1415
|
+
const quotaText = document.getElementById('quotaText');
|
|
1416
|
+
const quotaAvailable = document.getElementById('quotaAvailable');
|
|
1417
|
+
const quotaUsage = document.getElementById('quotaUsage');
|
|
1418
|
+
|
|
1419
|
+
if (quotaBar && quotaText && quotaAvailable) {
|
|
1420
|
+
const percentageValue = Math.round(percentage * 100);
|
|
1421
|
+
quotaBar.style.width = `${percentageValue}%`;
|
|
1422
|
+
|
|
1423
|
+
// Change color based on usage
|
|
1424
|
+
if (percentage > 0.8) {
|
|
1425
|
+
quotaBar.style.background = '#f44336'; // Red
|
|
1426
|
+
} else if (percentage > 0.6) {
|
|
1427
|
+
quotaBar.style.background = '#ff9800'; // Orange
|
|
1428
|
+
} else {
|
|
1429
|
+
quotaBar.style.background = '#4CAF50'; // Green
|
|
1430
|
+
}
|
|
1431
|
+
|
|
1432
|
+
quotaText.textContent = `${percentageValue}% used`;
|
|
1433
|
+
quotaAvailable.textContent = `${availableMB || 0} MB available`;
|
|
1434
|
+
}
|
|
1435
|
+
|
|
1436
|
+
if (quotaUsage) {
|
|
1437
|
+
quotaUsage.textContent = `${usageMB || 0} MB / ${quotaMB || 0} MB`;
|
|
1438
|
+
}
|
|
1439
|
+
}
|
|
1440
|
+
} catch (error) {
|
|
1441
|
+
console.error('Failed to update quota UI:', error);
|
|
1442
|
+
}
|
|
1443
|
+
}
|
|
1308
1444
|
|
|
1309
|
-
//
|
|
1310
|
-
|
|
1311
|
-
|
|
1312
|
-
|
|
1445
|
+
// Server polling control
|
|
1446
|
+
let serverPollingInterval = null;
|
|
1447
|
+
|
|
1448
|
+
function startPolling() {
|
|
1449
|
+
if (serverPollingInterval) {
|
|
1450
|
+
clearInterval(serverPollingInterval);
|
|
1451
|
+
}
|
|
1452
|
+
serverPollingInterval = setInterval(updateButtonStatesForServerStatus, 3000);
|
|
1453
|
+
console.log('Server polling started (every 3 seconds)');
|
|
1454
|
+
}
|
|
1455
|
+
|
|
1456
|
+
function stopPolling() {
|
|
1457
|
+
if (serverPollingInterval) {
|
|
1458
|
+
clearInterval(serverPollingInterval);
|
|
1459
|
+
serverPollingInterval = null;
|
|
1460
|
+
console.log('Server polling stopped');
|
|
1461
|
+
}
|
|
1462
|
+
}
|
|
1463
|
+
|
|
1464
|
+
async function checkServerOnce() {
|
|
1465
|
+
console.log('Running single server check...');
|
|
1466
|
+
await updateButtonStatesForServerStatus();
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
// Initialize polling controls
|
|
1470
|
+
const pollContinuouslyCheckbox = document.getElementById('pollContinuouslyCheckbox');
|
|
1471
|
+
const retryConnectionBtn = document.getElementById('retryConnectionBtn');
|
|
1472
|
+
|
|
1473
|
+
if (pollContinuouslyCheckbox && retryConnectionBtn) {
|
|
1474
|
+
// Load saved polling preference (default: true)
|
|
1475
|
+
chrome.storage.local.get(['pollContinuously'], (result) => {
|
|
1476
|
+
const shouldPoll = result.pollContinuously !== false; // Default to true
|
|
1477
|
+
pollContinuouslyCheckbox.checked = shouldPoll;
|
|
1478
|
+
|
|
1479
|
+
// Show/hide retry button based on preference
|
|
1480
|
+
retryConnectionBtn.style.display = shouldPoll ? 'none' : 'inline-block';
|
|
1481
|
+
|
|
1482
|
+
// Initial server check
|
|
1483
|
+
updateButtonStatesForServerStatus();
|
|
1484
|
+
|
|
1485
|
+
// Start polling if enabled
|
|
1486
|
+
if (shouldPoll) {
|
|
1487
|
+
startPolling();
|
|
1488
|
+
}
|
|
1489
|
+
});
|
|
1490
|
+
|
|
1491
|
+
// Checkbox change handler
|
|
1492
|
+
pollContinuouslyCheckbox.addEventListener('change', () => {
|
|
1493
|
+
const isChecked = pollContinuouslyCheckbox.checked;
|
|
1494
|
+
|
|
1495
|
+
// Save preference
|
|
1496
|
+
chrome.storage.local.set({ pollContinuously: isChecked });
|
|
1497
|
+
|
|
1498
|
+
// Show/hide retry button
|
|
1499
|
+
retryConnectionBtn.style.display = isChecked ? 'none' : 'inline-block';
|
|
1500
|
+
|
|
1501
|
+
// Start or stop polling
|
|
1502
|
+
if (isChecked) {
|
|
1503
|
+
startPolling();
|
|
1504
|
+
} else {
|
|
1505
|
+
stopPolling();
|
|
1506
|
+
}
|
|
1507
|
+
|
|
1508
|
+
console.log('Continuous polling:', isChecked ? 'enabled' : 'disabled');
|
|
1509
|
+
});
|
|
1510
|
+
|
|
1511
|
+
// Retry button click handler
|
|
1512
|
+
retryConnectionBtn.addEventListener('click', () => {
|
|
1513
|
+
showToast('Retrying connection...', 2000, 'info');
|
|
1514
|
+
checkServerOnce();
|
|
1515
|
+
});
|
|
1516
|
+
} else {
|
|
1517
|
+
// Fallback if polling controls not found - use default behavior
|
|
1518
|
+
console.warn('Polling controls not found, using default polling behavior');
|
|
1519
|
+
updateButtonStatesForServerStatus();
|
|
1520
|
+
setInterval(updateButtonStatesForServerStatus, 3000);
|
|
1521
|
+
}
|
|
1522
|
+
|
|
1313
1523
|
// Initialize site management functionality
|
|
1314
1524
|
initializeSiteManagement();
|
|
1315
1525
|
|
|
@@ -1414,9 +1624,13 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1414
1624
|
}
|
|
1415
1625
|
|
|
1416
1626
|
});
|
|
1417
|
-
|
|
1418
|
-
// Load saved recordings
|
|
1627
|
+
|
|
1628
|
+
// Load saved recordings (legacy)
|
|
1419
1629
|
loadRecordings();
|
|
1630
|
+
|
|
1631
|
+
// Load screen and workflow recordings with saved preferences
|
|
1632
|
+
loadScreenRecordings();
|
|
1633
|
+
loadWorkflowRecordings();
|
|
1420
1634
|
|
|
1421
1635
|
// Setup recording settings event listeners
|
|
1422
1636
|
const frameRateSelect = document.getElementById('frameRate');
|
|
@@ -1488,19 +1702,15 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1488
1702
|
try {
|
|
1489
1703
|
// Check if server is running before starting recording
|
|
1490
1704
|
const serverStatus = await checkServerStatus();
|
|
1491
|
-
|
|
1492
|
-
|
|
1493
|
-
|
|
1494
|
-
|
|
1495
|
-
|
|
1496
|
-
|
|
1497
|
-
|
|
1498
|
-
setTimeout(() => {
|
|
1499
|
-
if (recordingStatus) recordingStatus.textContent = '';
|
|
1500
|
-
}, 5000);
|
|
1501
|
-
return;
|
|
1705
|
+
const isBrowserOnlyMode = !serverStatus.connected;
|
|
1706
|
+
|
|
1707
|
+
if (isBrowserOnlyMode) {
|
|
1708
|
+
console.log('Starting recording in browser-only mode');
|
|
1709
|
+
// Browser-only mode - proceed with local storage
|
|
1710
|
+
} else {
|
|
1711
|
+
console.log('Starting recording with server');
|
|
1502
1712
|
}
|
|
1503
|
-
|
|
1713
|
+
|
|
1504
1714
|
// Get the current active tab
|
|
1505
1715
|
const [tab] = await chrome.tabs.query({ active: true, currentWindow: true });
|
|
1506
1716
|
console.log('Current tab:', tab);
|
|
@@ -1531,10 +1741,11 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1531
1741
|
};
|
|
1532
1742
|
|
|
1533
1743
|
// Send message to background to start recording
|
|
1534
|
-
chrome.runtime.sendMessage({
|
|
1535
|
-
action: 'startRecording',
|
|
1744
|
+
chrome.runtime.sendMessage({
|
|
1745
|
+
action: 'startRecording',
|
|
1536
1746
|
tabId: tab.id,
|
|
1537
|
-
settings: settings
|
|
1747
|
+
settings: settings,
|
|
1748
|
+
mode: isBrowserOnlyMode ? 'browser-only' : 'server'
|
|
1538
1749
|
}, (response) => {
|
|
1539
1750
|
recordBtn.disabled = false;
|
|
1540
1751
|
|
|
@@ -1574,23 +1785,47 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1574
1785
|
recordBtn.textContent = 'Stopping...';
|
|
1575
1786
|
recordBtn.disabled = true;
|
|
1576
1787
|
|
|
1577
|
-
chrome.runtime.sendMessage({
|
|
1788
|
+
chrome.runtime.sendMessage({
|
|
1578
1789
|
action: 'stopRecording'
|
|
1579
|
-
}, (response) => {
|
|
1790
|
+
}, async (response) => {
|
|
1580
1791
|
isStoppingRecording = false;
|
|
1581
1792
|
recordBtn.disabled = false;
|
|
1582
|
-
|
|
1793
|
+
|
|
1583
1794
|
if (chrome.runtime.lastError) {
|
|
1584
1795
|
console.error('Error stopping recording:', chrome.runtime.lastError);
|
|
1585
1796
|
recordingStatus.textContent = 'Error: ' + chrome.runtime.lastError.message;
|
|
1586
1797
|
recordBtn.textContent = 'Stop Recording';
|
|
1587
1798
|
return;
|
|
1588
1799
|
}
|
|
1589
|
-
|
|
1800
|
+
|
|
1590
1801
|
if (response && response.success) {
|
|
1591
|
-
console.log('Recording stopped successfully');
|
|
1802
|
+
console.log('Recording stopped successfully', response);
|
|
1592
1803
|
isRecording = false;
|
|
1593
1804
|
updateRecordingUI();
|
|
1805
|
+
|
|
1806
|
+
console.log('About to show loading overlay for screen-recording-section');
|
|
1807
|
+
// Show loading overlay over screen recording section
|
|
1808
|
+
showSectionLoading('screen-recording-section', 'Saving screen recording...');
|
|
1809
|
+
console.log('Loading overlay should be visible now');
|
|
1810
|
+
|
|
1811
|
+
// Use then/catch instead of async/await since we're in a callback
|
|
1812
|
+
loadScreenRecordings(true)
|
|
1813
|
+
.then(() => {
|
|
1814
|
+
console.log('Screen recordings loaded successfully');
|
|
1815
|
+
// Show success toast
|
|
1816
|
+
const frameCount = response.frameCount || 0;
|
|
1817
|
+
const duration = response.duration || '0s';
|
|
1818
|
+
showToast(`Screen recording saved! ${frameCount} frames (${duration})`, 3000, 'success');
|
|
1819
|
+
})
|
|
1820
|
+
.catch(error => {
|
|
1821
|
+
console.error('Error loading screen recordings:', error);
|
|
1822
|
+
showToast('Error loading recordings. Please refresh.', 3000, 'error');
|
|
1823
|
+
})
|
|
1824
|
+
.finally(() => {
|
|
1825
|
+
console.log('Hiding loading overlay');
|
|
1826
|
+
// Always hide loading overlay
|
|
1827
|
+
hideSectionLoading('screen-recording-section');
|
|
1828
|
+
});
|
|
1594
1829
|
} else {
|
|
1595
1830
|
recordingStatus.textContent = 'Error: ' + (response?.error || 'Failed to stop recording');
|
|
1596
1831
|
recordBtn.textContent = 'Stop Recording';
|
|
@@ -1822,86 +2057,29 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
1822
2057
|
if (response && response.success && response.workflow) {
|
|
1823
2058
|
isWorkflowRecording = false;
|
|
1824
2059
|
// Clear state from storage
|
|
1825
|
-
chrome.storage.local.set({
|
|
2060
|
+
chrome.storage.local.set({
|
|
1826
2061
|
workflowRecording: false,
|
|
1827
2062
|
workflowStartTime: null
|
|
1828
2063
|
});
|
|
1829
2064
|
updateWorkflowRecordingUI();
|
|
1830
|
-
|
|
1831
|
-
//
|
|
1832
|
-
|
|
1833
|
-
|
|
1834
|
-
|
|
1835
|
-
//
|
|
1836
|
-
|
|
1837
|
-
|
|
1838
|
-
|
|
1839
|
-
// The response.workflow contains the full response from stopWorkflowRecording
|
|
2065
|
+
|
|
2066
|
+
// Show loading overlay over workflow recording section
|
|
2067
|
+
showSectionLoading('workflow-recording-section', 'Saving workflow recording...');
|
|
2068
|
+
|
|
2069
|
+
try {
|
|
2070
|
+
// Single source of truth - load from server
|
|
2071
|
+
await loadWorkflowRecordings(true);
|
|
2072
|
+
|
|
2073
|
+
// Show success toast
|
|
1840
2074
|
const workflowData = response.workflow;
|
|
1841
|
-
|
|
1842
|
-
|
|
1843
|
-
|
|
1844
|
-
|
|
1845
|
-
|
|
1846
|
-
|
|
1847
|
-
|
|
1848
|
-
|
|
1849
|
-
${workflowData.workflow.sessionId} (workflow)
|
|
1850
|
-
</div>
|
|
1851
|
-
<div style="color: #666; font-size: 11px;">
|
|
1852
|
-
${workflowData.workflow.actions.length} actions • ${workflowData.workflow.logs.length} logs
|
|
1853
|
-
</div>
|
|
1854
|
-
</div>
|
|
1855
|
-
<div style="display: flex; gap: 4px; margin-top: 8px;">
|
|
1856
|
-
<button class="copy-id-btn" style="padding: 4px 8px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
|
|
1857
|
-
Copy ID
|
|
1858
|
-
</button>
|
|
1859
|
-
<button class="copy-prompt-btn" data-id="${workflowData.workflow.sessionId}" data-port="${currentServerPort || ''}" style="padding: 4px 8px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
|
|
1860
|
-
Copy Prompt
|
|
1861
|
-
</button>
|
|
1862
|
-
</div>
|
|
1863
|
-
`;
|
|
1864
|
-
|
|
1865
|
-
// Add event listeners for the buttons
|
|
1866
|
-
const copyIdBtn = workflowItem.querySelector('.copy-id-btn');
|
|
1867
|
-
const copyPromptBtn = workflowItem.querySelector('.copy-prompt-btn');
|
|
1868
|
-
|
|
1869
|
-
copyIdBtn.addEventListener('click', () => {
|
|
1870
|
-
navigator.clipboard.writeText(workflowData.workflow.sessionId).then(() => {
|
|
1871
|
-
copyIdBtn.textContent = 'Copied!';
|
|
1872
|
-
copyIdBtn.style.background = '#4CAF50';
|
|
1873
|
-
setTimeout(() => {
|
|
1874
|
-
copyIdBtn.textContent = 'Copy ID';
|
|
1875
|
-
copyIdBtn.style.background = '#2196F3';
|
|
1876
|
-
}, 2000);
|
|
1877
|
-
});
|
|
1878
|
-
});
|
|
1879
|
-
|
|
1880
|
-
// copyPromptBtn is now handled by the shared event listener system with data-id and data-port attributes
|
|
1881
|
-
} else {
|
|
1882
|
-
// Fallback when server not available
|
|
1883
|
-
workflowItem = document.createElement('div');
|
|
1884
|
-
workflowItem.className = 'recording-item';
|
|
1885
|
-
workflowItem.innerHTML = `
|
|
1886
|
-
<div class="recording-info">
|
|
1887
|
-
<div style="font-weight: bold; margin-bottom: 4px; color: #ff6b6b;">
|
|
1888
|
-
Server not available - JSON downloaded
|
|
1889
|
-
</div>
|
|
1890
|
-
<div style="color: #666; font-size: 11px;">
|
|
1891
|
-
${workflowData.workflow.length} actions recorded
|
|
1892
|
-
</div>
|
|
1893
|
-
</div>
|
|
1894
|
-
`;
|
|
1895
|
-
}
|
|
1896
|
-
|
|
1897
|
-
// Add the item to the container
|
|
1898
|
-
workflowRecordingsContainer.appendChild(workflowItem);
|
|
1899
|
-
workflowRecordingsList.style.display = 'block';
|
|
1900
|
-
|
|
1901
|
-
// Reload the list to show all recordings
|
|
1902
|
-
setTimeout(() => {
|
|
1903
|
-
loadWorkflowRecordings();
|
|
1904
|
-
}, 500);
|
|
2075
|
+
const actionCount = workflowData?.workflow?.actions?.length || workflowData?.workflow?.length || 0;
|
|
2076
|
+
showToast(`Workflow recording saved! ${actionCount} actions recorded`, 3000, 'success');
|
|
2077
|
+
} catch (error) {
|
|
2078
|
+
console.error('Error loading workflow recordings:', error);
|
|
2079
|
+
showToast('Error loading recordings. Please refresh.', 3000, 'error');
|
|
2080
|
+
} finally {
|
|
2081
|
+
// Always hide loading overlay
|
|
2082
|
+
hideSectionLoading('workflow-recording-section');
|
|
1905
2083
|
}
|
|
1906
2084
|
}
|
|
1907
2085
|
});
|
|
@@ -2193,34 +2371,46 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2193
2371
|
const viewScreenRecordingsLink = document.getElementById('viewScreenRecordings');
|
|
2194
2372
|
|
|
2195
2373
|
if (viewWorkflowRecordingsLink) {
|
|
2196
|
-
viewWorkflowRecordingsLink.addEventListener('click', (e) => {
|
|
2374
|
+
viewWorkflowRecordingsLink.addEventListener('click', async (e) => {
|
|
2197
2375
|
e.preventDefault();
|
|
2198
2376
|
const workflowRecordingsList = document.getElementById('workflowRecordingsList');
|
|
2199
2377
|
if (workflowRecordingsList) {
|
|
2200
2378
|
if (workflowRecordingsList.style.display === 'none' || workflowRecordingsList.style.display === '') {
|
|
2379
|
+
// Show recordings
|
|
2201
2380
|
workflowRecordingsList.style.display = 'block';
|
|
2202
|
-
loadWorkflowRecordings();
|
|
2381
|
+
await loadWorkflowRecordings();
|
|
2203
2382
|
viewWorkflowRecordingsLink.textContent = 'Hide Recordings';
|
|
2383
|
+
// Save preference
|
|
2384
|
+
await chrome.storage.local.set({ workflowRecordingsVisible: true });
|
|
2204
2385
|
} else {
|
|
2386
|
+
// Hide recordings
|
|
2205
2387
|
workflowRecordingsList.style.display = 'none';
|
|
2206
2388
|
viewWorkflowRecordingsLink.textContent = 'View Past Recordings';
|
|
2389
|
+
// Save preference
|
|
2390
|
+
await chrome.storage.local.set({ workflowRecordingsVisible: false });
|
|
2207
2391
|
}
|
|
2208
2392
|
}
|
|
2209
2393
|
});
|
|
2210
2394
|
}
|
|
2211
2395
|
|
|
2212
2396
|
if (viewScreenRecordingsLink) {
|
|
2213
|
-
viewScreenRecordingsLink.addEventListener('click', (e) => {
|
|
2397
|
+
viewScreenRecordingsLink.addEventListener('click', async (e) => {
|
|
2214
2398
|
e.preventDefault();
|
|
2215
2399
|
const recordingsList = document.getElementById('recordingsList');
|
|
2216
2400
|
if (recordingsList) {
|
|
2217
2401
|
if (recordingsList.style.display === 'none') {
|
|
2218
|
-
|
|
2402
|
+
// Show recordings
|
|
2403
|
+
await loadScreenRecordings();
|
|
2219
2404
|
recordingsList.style.display = 'block';
|
|
2220
2405
|
viewScreenRecordingsLink.textContent = 'Hide Recordings';
|
|
2406
|
+
// Save preference
|
|
2407
|
+
await chrome.storage.local.set({ screenRecordingsVisible: true });
|
|
2221
2408
|
} else {
|
|
2409
|
+
// Hide recordings
|
|
2222
2410
|
recordingsList.style.display = 'none';
|
|
2223
2411
|
viewScreenRecordingsLink.textContent = 'View Past Recordings';
|
|
2412
|
+
// Save preference
|
|
2413
|
+
await chrome.storage.local.set({ screenRecordingsVisible: false });
|
|
2224
2414
|
}
|
|
2225
2415
|
}
|
|
2226
2416
|
});
|
|
@@ -2261,16 +2451,19 @@ document.addEventListener('DOMContentLoaded', () => {
|
|
|
2261
2451
|
});
|
|
2262
2452
|
|
|
2263
2453
|
// Function to load and display workflow recordings
|
|
2264
|
-
async function loadWorkflowRecordings() {
|
|
2454
|
+
async function loadWorkflowRecordings(autoShow = false) {
|
|
2265
2455
|
const workflowRecordingsList = document.getElementById('workflowRecordingsList');
|
|
2266
2456
|
const workflowRecordingsContainer = document.getElementById('workflowRecordingsContainer');
|
|
2267
|
-
|
|
2457
|
+
const viewWorkflowRecordingsLink = document.getElementById('viewWorkflowRecordings');
|
|
2458
|
+
|
|
2268
2459
|
if (!workflowRecordingsList || !workflowRecordingsContainer) {
|
|
2269
2460
|
return;
|
|
2270
2461
|
}
|
|
2271
|
-
|
|
2462
|
+
|
|
2272
2463
|
// Clear existing items
|
|
2273
2464
|
workflowRecordingsContainer.innerHTML = '';
|
|
2465
|
+
|
|
2466
|
+
let hasRecordings = false;
|
|
2274
2467
|
|
|
2275
2468
|
// Check server for workflow recordings
|
|
2276
2469
|
const ports = CONFIG_PORTS;
|
|
@@ -2283,8 +2476,9 @@ async function loadWorkflowRecordings() {
|
|
|
2283
2476
|
|
|
2284
2477
|
// Handle both data.recordings and direct array response
|
|
2285
2478
|
const recordings = data.recordings || data;
|
|
2286
|
-
|
|
2479
|
+
|
|
2287
2480
|
if (Array.isArray(recordings) && recordings.length > 0) {
|
|
2481
|
+
hasRecordings = true;
|
|
2288
2482
|
// Display each workflow recording
|
|
2289
2483
|
recordings.forEach(recording => {
|
|
2290
2484
|
const workflowItem = document.createElement('div');
|
|
@@ -2303,12 +2497,18 @@ async function loadWorkflowRecordings() {
|
|
|
2303
2497
|
${recording.screenshot_settings ? '<div style="color: #2196F3; font-size: 10px;">📸 Screenshots included</div>' : ''}
|
|
2304
2498
|
</div>
|
|
2305
2499
|
<div class="recording-buttons">
|
|
2500
|
+
<button class="copy-id-btn" data-session-id="${recording.session_id}" style="padding: 4px 8px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
|
|
2501
|
+
Copy ID
|
|
2502
|
+
</button>
|
|
2503
|
+
<button class="copy-prompt-btn" data-session-id="${recording.session_id}" data-name="${recording.name || ''}" style="padding: 4px 8px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
|
|
2504
|
+
Copy Prompt
|
|
2505
|
+
</button>
|
|
2506
|
+
<button class="view-btn" data-session-id="${recording.session_id}" style="padding: 4px 8px; background: #9C27B0; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
|
|
2507
|
+
View
|
|
2508
|
+
</button>
|
|
2306
2509
|
<button class="play-btn" data-session-id="${recording.session_id}" style="padding: 4px 8px; background: #4CAF50; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
|
|
2307
2510
|
▶ Play
|
|
2308
2511
|
</button>
|
|
2309
|
-
<button class="get-prompt-btn" data-session-id="${recording.session_id}" data-name="${recording.name || ''}" style="padding: 4px 8px; background: #2196F3; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
|
|
2310
|
-
Get Prompt
|
|
2311
|
-
</button>
|
|
2312
2512
|
<button class="delete-btn" data-recording-id="${recording.id || recording.session_id}" style="padding: 4px 8px; background: #f44336; color: white; border: none; border-radius: 3px; cursor: pointer; font-size: 11px;">
|
|
2313
2513
|
Delete
|
|
2314
2514
|
</button>
|
|
@@ -2316,17 +2516,29 @@ async function loadWorkflowRecordings() {
|
|
|
2316
2516
|
`;
|
|
2317
2517
|
|
|
2318
2518
|
// Add event listeners
|
|
2519
|
+
const copyIdBtn = workflowItem.querySelector('.copy-id-btn');
|
|
2520
|
+
const copyPromptBtn = workflowItem.querySelector('.copy-prompt-btn');
|
|
2521
|
+
const viewBtn = workflowItem.querySelector('.view-btn');
|
|
2319
2522
|
const playBtn = workflowItem.querySelector('.play-btn');
|
|
2320
|
-
const getPromptBtn = workflowItem.querySelector('.get-prompt-btn');
|
|
2321
2523
|
const deleteBtn = workflowItem.querySelector('.delete-btn');
|
|
2322
|
-
|
|
2323
|
-
|
|
2324
|
-
|
|
2524
|
+
|
|
2525
|
+
copyIdBtn.addEventListener('click', () => {
|
|
2526
|
+
navigator.clipboard.writeText(recording.session_id).then(() => {
|
|
2527
|
+
copyIdBtn.textContent = 'Copied!';
|
|
2528
|
+
copyIdBtn.style.background = '#4CAF50';
|
|
2529
|
+
setTimeout(() => {
|
|
2530
|
+
copyIdBtn.textContent = 'Copy ID';
|
|
2531
|
+
copyIdBtn.style.background = '#2196F3';
|
|
2532
|
+
}, 2000);
|
|
2533
|
+
});
|
|
2534
|
+
});
|
|
2535
|
+
|
|
2536
|
+
copyPromptBtn.addEventListener('click', () => {
|
|
2325
2537
|
const sessionId = recording.session_id;
|
|
2326
2538
|
const name = recording.name;
|
|
2327
2539
|
const port = window.CHROMEDEBUG_CONFIG?.ports?.[0] || '3001';
|
|
2328
2540
|
let prompt;
|
|
2329
|
-
|
|
2541
|
+
|
|
2330
2542
|
if (name) {
|
|
2331
2543
|
// Use name-based prompt similar to screen recording format
|
|
2332
2544
|
prompt = `Please use the get_workflow_recording function in Chrome Debug to load the workflow recording "${name}" (session: ${sessionId}) that was recorded on port ${port}.`;
|
|
@@ -2334,16 +2546,26 @@ async function loadWorkflowRecordings() {
|
|
|
2334
2546
|
// Use session ID based prompt
|
|
2335
2547
|
prompt = `Please use the get_workflow_recording function in Chrome Debug to load the workflow recording "${sessionId}" that was recorded on port ${port}.`;
|
|
2336
2548
|
}
|
|
2337
|
-
|
|
2549
|
+
|
|
2338
2550
|
navigator.clipboard.writeText(prompt).then(() => {
|
|
2339
|
-
|
|
2340
|
-
|
|
2551
|
+
copyPromptBtn.textContent = 'Copied!';
|
|
2552
|
+
const originalBg = copyPromptBtn.style.background;
|
|
2553
|
+
copyPromptBtn.style.background = '#4CAF50';
|
|
2341
2554
|
setTimeout(() => {
|
|
2342
|
-
|
|
2343
|
-
|
|
2555
|
+
copyPromptBtn.textContent = 'Copy Prompt';
|
|
2556
|
+
copyPromptBtn.style.background = originalBg;
|
|
2344
2557
|
}, 2000);
|
|
2345
2558
|
});
|
|
2346
2559
|
});
|
|
2560
|
+
|
|
2561
|
+
viewBtn.addEventListener('click', async () => {
|
|
2562
|
+
// Open frame editor in workflow mode
|
|
2563
|
+
const frameEditorUrl = chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${recording.session_id}&type=workflow`);
|
|
2564
|
+
chrome.tabs.create({ url: frameEditorUrl });
|
|
2565
|
+
});
|
|
2566
|
+
|
|
2567
|
+
playBtn.addEventListener('click', () => playWorkflow(recording.session_id));
|
|
2568
|
+
|
|
2347
2569
|
deleteBtn.addEventListener('click', () => {
|
|
2348
2570
|
console.log('Delete clicked for recording:', recording);
|
|
2349
2571
|
const recordingId = recording.id || recording.session_id;
|
|
@@ -2378,6 +2600,27 @@ async function loadWorkflowRecordings() {
|
|
|
2378
2600
|
errorMsg.textContent = 'Could not connect to Chrome Debug server';
|
|
2379
2601
|
workflowRecordingsContainer.appendChild(errorMsg);
|
|
2380
2602
|
}
|
|
2603
|
+
|
|
2604
|
+
// Handle visibility based on recordings and user preference
|
|
2605
|
+
if (!hasRecordings) {
|
|
2606
|
+
workflowRecordingsList.style.display = 'none';
|
|
2607
|
+
} else {
|
|
2608
|
+
// Get user's saved preference
|
|
2609
|
+
const prefs = await chrome.storage.local.get(['workflowRecordingsVisible']);
|
|
2610
|
+
const shouldShow = autoShow || (prefs.workflowRecordingsVisible !== false); // Default to true
|
|
2611
|
+
|
|
2612
|
+
if (shouldShow) {
|
|
2613
|
+
workflowRecordingsList.style.display = 'block';
|
|
2614
|
+
if (viewWorkflowRecordingsLink) {
|
|
2615
|
+
viewWorkflowRecordingsLink.textContent = 'Hide Recordings';
|
|
2616
|
+
}
|
|
2617
|
+
} else {
|
|
2618
|
+
workflowRecordingsList.style.display = 'none';
|
|
2619
|
+
if (viewWorkflowRecordingsLink) {
|
|
2620
|
+
viewWorkflowRecordingsLink.textContent = 'View Past Recordings';
|
|
2621
|
+
}
|
|
2622
|
+
}
|
|
2623
|
+
}
|
|
2381
2624
|
}
|
|
2382
2625
|
|
|
2383
2626
|
// Function to play a workflow
|
|
@@ -2526,18 +2769,147 @@ async function deleteWorkflowRecording(recordingId) {
|
|
|
2526
2769
|
}
|
|
2527
2770
|
|
|
2528
2771
|
// Function to load and display screen recordings
|
|
2529
|
-
async function loadScreenRecordings() {
|
|
2772
|
+
async function loadScreenRecordings(autoShow = false) {
|
|
2530
2773
|
const recordingsList = document.getElementById('recordingsList');
|
|
2531
2774
|
const recordingsContainer = document.getElementById('recordingsContainer');
|
|
2532
|
-
|
|
2775
|
+
const viewScreenRecordingsLink = document.getElementById('viewScreenRecordings');
|
|
2776
|
+
|
|
2533
2777
|
if (!recordingsList || !recordingsContainer) {
|
|
2534
2778
|
return;
|
|
2535
2779
|
}
|
|
2536
|
-
|
|
2780
|
+
|
|
2537
2781
|
// Clear existing items
|
|
2538
2782
|
recordingsContainer.innerHTML = '';
|
|
2539
|
-
|
|
2540
|
-
|
|
2783
|
+
|
|
2784
|
+
let hasRecordings = false;
|
|
2785
|
+
|
|
2786
|
+
// First, load browser-only recordings from IndexedDB
|
|
2787
|
+
try {
|
|
2788
|
+
console.log('[Popup] Requesting browser-only recordings...');
|
|
2789
|
+
const browserRecordings = await chrome.runtime.sendMessage({
|
|
2790
|
+
action: 'getBrowserRecordings'
|
|
2791
|
+
});
|
|
2792
|
+
console.log('[Popup] Browser recordings received:', browserRecordings);
|
|
2793
|
+
|
|
2794
|
+
if (browserRecordings && browserRecordings.length > 0) {
|
|
2795
|
+
hasRecordings = true;
|
|
2796
|
+
recordingsList.style.display = 'block';
|
|
2797
|
+
|
|
2798
|
+
// Display each browser-only recording
|
|
2799
|
+
browserRecordings.forEach(recording => {
|
|
2800
|
+
const recordingItem = document.createElement('div');
|
|
2801
|
+
recordingItem.className = 'recording-item';
|
|
2802
|
+
|
|
2803
|
+
const date = new Date(recording.startTime);
|
|
2804
|
+
const formattedDate = date.toLocaleString();
|
|
2805
|
+
const displayName = recording.title || `Recording ${recording.sessionId}`;
|
|
2806
|
+
|
|
2807
|
+
recordingItem.innerHTML = `
|
|
2808
|
+
<div class="recording-info">
|
|
2809
|
+
<div class="recording-id" title="${recording.sessionId}">
|
|
2810
|
+
<span class="recording-id-text">🌐 ${displayName}</span>
|
|
2811
|
+
<span style="font-size: 10px; background: #ff9800; color: white; padding: 2px 6px; border-radius: 3px; margin-left: 4px;">BROWSER-ONLY</span>
|
|
2812
|
+
<span style="color: #666; font-size: 11px; margin-left: 8px;">${recording.frameCount || 0} frames • ${formattedDate}</span>
|
|
2813
|
+
</div>
|
|
2814
|
+
</div>
|
|
2815
|
+
<div class="recording-buttons">
|
|
2816
|
+
<button class="copy-id-btn" data-session-id="${recording.sessionId}" title="Copy ID">Copy ID</button>
|
|
2817
|
+
<button class="copy-prompt-btn" data-session-id="${recording.sessionId}" data-name="${recording.title || ''}" title="Copy Claude Prompt">Copy Prompt</button>
|
|
2818
|
+
<button class="view-browser-btn" data-session-id="${recording.sessionId}" title="View Screenshots">View</button>
|
|
2819
|
+
<button class="export-btn" data-session-id="${recording.sessionId}" title="Export Recording">Export</button>
|
|
2820
|
+
<button class="delete-browser-btn" data-session-id="${recording.sessionId}" title="Delete">Delete</button>
|
|
2821
|
+
</div>
|
|
2822
|
+
`;
|
|
2823
|
+
|
|
2824
|
+
// Add event listeners
|
|
2825
|
+
const copyIdBtn = recordingItem.querySelector('.copy-id-btn');
|
|
2826
|
+
const copyPromptBtn = recordingItem.querySelector('.copy-prompt-btn');
|
|
2827
|
+
const viewBtn = recordingItem.querySelector('.view-browser-btn');
|
|
2828
|
+
const exportBtn = recordingItem.querySelector('.export-btn');
|
|
2829
|
+
const deleteBtn = recordingItem.querySelector('.delete-browser-btn');
|
|
2830
|
+
|
|
2831
|
+
copyIdBtn.addEventListener('click', () => {
|
|
2832
|
+
navigator.clipboard.writeText(recording.sessionId).then(() => {
|
|
2833
|
+
copyIdBtn.textContent = 'Copied!';
|
|
2834
|
+
copyIdBtn.style.background = '#4CAF50';
|
|
2835
|
+
setTimeout(() => {
|
|
2836
|
+
copyIdBtn.textContent = 'Copy ID';
|
|
2837
|
+
copyIdBtn.style.background = '#2196F3';
|
|
2838
|
+
}, 2000);
|
|
2839
|
+
});
|
|
2840
|
+
});
|
|
2841
|
+
|
|
2842
|
+
copyPromptBtn.addEventListener('click', () => {
|
|
2843
|
+
const sessionId = recording.sessionId;
|
|
2844
|
+
const title = recording.title;
|
|
2845
|
+
let prompt;
|
|
2846
|
+
|
|
2847
|
+
if (title) {
|
|
2848
|
+
prompt = `Please analyze the browser-only screen recording "${title}" (session: ${sessionId}). This recording is stored locally in the browser.`;
|
|
2849
|
+
} else {
|
|
2850
|
+
prompt = `Please analyze the browser-only screen recording "${sessionId}". This recording is stored locally in the browser.`;
|
|
2851
|
+
}
|
|
2852
|
+
|
|
2853
|
+
navigator.clipboard.writeText(prompt).then(() => {
|
|
2854
|
+
copyPromptBtn.textContent = 'Copied!';
|
|
2855
|
+
const originalBg = copyPromptBtn.style.background;
|
|
2856
|
+
copyPromptBtn.style.background = '#4CAF50';
|
|
2857
|
+
setTimeout(() => {
|
|
2858
|
+
copyPromptBtn.textContent = 'Copy Prompt';
|
|
2859
|
+
copyPromptBtn.style.background = originalBg;
|
|
2860
|
+
}, 2000);
|
|
2861
|
+
});
|
|
2862
|
+
});
|
|
2863
|
+
|
|
2864
|
+
viewBtn.addEventListener('click', () => {
|
|
2865
|
+
// Open frame editor in browser-only mode
|
|
2866
|
+
const frameEditorUrl = chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${recording.sessionId}&browserOnly=true`);
|
|
2867
|
+
chrome.tabs.create({ url: frameEditorUrl });
|
|
2868
|
+
});
|
|
2869
|
+
|
|
2870
|
+
exportBtn.addEventListener('click', async () => {
|
|
2871
|
+
try {
|
|
2872
|
+
const result = await chrome.runtime.sendMessage({
|
|
2873
|
+
action: 'exportBrowserRecording',
|
|
2874
|
+
sessionId: recording.sessionId
|
|
2875
|
+
});
|
|
2876
|
+
|
|
2877
|
+
if (result && result.success) {
|
|
2878
|
+
exportBtn.textContent = 'Exported!';
|
|
2879
|
+
exportBtn.style.background = '#4CAF50';
|
|
2880
|
+
setTimeout(() => {
|
|
2881
|
+
exportBtn.textContent = 'Export';
|
|
2882
|
+
exportBtn.style.background = '#FF9800';
|
|
2883
|
+
}, 2000);
|
|
2884
|
+
}
|
|
2885
|
+
} catch (error) {
|
|
2886
|
+
console.error('Failed to export recording:', error);
|
|
2887
|
+
alert('Failed to export recording');
|
|
2888
|
+
}
|
|
2889
|
+
});
|
|
2890
|
+
|
|
2891
|
+
deleteBtn.addEventListener('click', async () => {
|
|
2892
|
+
if (confirm('Are you sure you want to delete this browser recording?')) {
|
|
2893
|
+
try {
|
|
2894
|
+
await chrome.runtime.sendMessage({
|
|
2895
|
+
action: 'deleteBrowserRecording',
|
|
2896
|
+
sessionId: recording.sessionId
|
|
2897
|
+
});
|
|
2898
|
+
loadScreenRecordings(); // Reload list
|
|
2899
|
+
} catch (error) {
|
|
2900
|
+
console.error('Failed to delete recording:', error);
|
|
2901
|
+
}
|
|
2902
|
+
}
|
|
2903
|
+
});
|
|
2904
|
+
|
|
2905
|
+
recordingsContainer.appendChild(recordingItem);
|
|
2906
|
+
});
|
|
2907
|
+
}
|
|
2908
|
+
} catch (error) {
|
|
2909
|
+
console.error('Failed to load browser-only recordings:', error);
|
|
2910
|
+
}
|
|
2911
|
+
|
|
2912
|
+
// Then, check server for screen recordings
|
|
2541
2913
|
const ports = CONFIG_PORTS;
|
|
2542
2914
|
for (const port of ports) {
|
|
2543
2915
|
try {
|
|
@@ -2545,6 +2917,7 @@ async function loadScreenRecordings() {
|
|
|
2545
2917
|
if (response.ok) {
|
|
2546
2918
|
const data = await response.json();
|
|
2547
2919
|
if (data && data.length > 0) {
|
|
2920
|
+
hasRecordings = true;
|
|
2548
2921
|
recordingsList.style.display = 'block';
|
|
2549
2922
|
|
|
2550
2923
|
// Display each screen recording
|
|
@@ -2563,25 +2936,25 @@ async function loadScreenRecordings() {
|
|
|
2563
2936
|
|
|
2564
2937
|
recordingItem.innerHTML = `
|
|
2565
2938
|
<div class="recording-info">
|
|
2566
|
-
<div
|
|
2567
|
-
|
|
2568
|
-
|
|
2939
|
+
<div class="recording-id" title="${recording.sessionId}">
|
|
2940
|
+
<span class="recording-id-text">🎥 ${displayName}</span>
|
|
2941
|
+
<span style="color: #666; font-size: 11px; margin-left: 8px;">${recording.totalFrames} frames • ${formattedDate}</span>
|
|
2569
2942
|
</div>
|
|
2570
2943
|
</div>
|
|
2571
2944
|
<div class="recording-buttons">
|
|
2572
|
-
<button class="copy-id-btn" data-session-id="${recording.sessionId}"
|
|
2573
|
-
|
|
2574
|
-
</button>
|
|
2575
|
-
<button class="delete-btn" data-session-id="${recording.sessionId}"
|
|
2576
|
-
Delete
|
|
2577
|
-
</button>
|
|
2945
|
+
<button class="copy-id-btn" data-session-id="${recording.sessionId}" title="Copy ID">Copy ID</button>
|
|
2946
|
+
<button class="copy-prompt-btn" data-session-id="${recording.sessionId}" data-name="${recording.name || ''}" data-port="${port}" title="Copy Claude Prompt">Copy Prompt</button>
|
|
2947
|
+
<button class="view-btn" data-session-id="${recording.sessionId}" title="View Screenshots">View</button>
|
|
2948
|
+
<button class="delete-btn" data-session-id="${recording.sessionId}" title="Delete">Delete</button>
|
|
2578
2949
|
</div>
|
|
2579
2950
|
`;
|
|
2580
2951
|
|
|
2581
2952
|
// Add event listeners
|
|
2582
2953
|
const copyIdBtn = recordingItem.querySelector('.copy-id-btn');
|
|
2954
|
+
const copyPromptBtn = recordingItem.querySelector('.copy-prompt-btn');
|
|
2955
|
+
const viewBtn = recordingItem.querySelector('.view-btn');
|
|
2583
2956
|
const deleteBtn = recordingItem.querySelector('.delete-btn');
|
|
2584
|
-
|
|
2957
|
+
|
|
2585
2958
|
copyIdBtn.addEventListener('click', () => {
|
|
2586
2959
|
navigator.clipboard.writeText(recording.sessionId).then(() => {
|
|
2587
2960
|
copyIdBtn.textContent = 'Copied!';
|
|
@@ -2592,14 +2965,60 @@ async function loadScreenRecordings() {
|
|
|
2592
2965
|
}, 2000);
|
|
2593
2966
|
});
|
|
2594
2967
|
});
|
|
2595
|
-
|
|
2968
|
+
|
|
2969
|
+
copyPromptBtn.addEventListener('click', () => {
|
|
2970
|
+
const sessionId = recording.sessionId;
|
|
2971
|
+
const name = recording.name;
|
|
2972
|
+
let prompt;
|
|
2973
|
+
|
|
2974
|
+
if (name) {
|
|
2975
|
+
prompt = `Please use the chrome_pilot_show_frames function in Chrome Debug to display the screen recording "${name}" (session: ${sessionId}) that was recorded on port ${port}.`;
|
|
2976
|
+
} else {
|
|
2977
|
+
prompt = `Please use the chrome_pilot_show_frames function in Chrome Debug to display the screen recording "${sessionId}" that was recorded on port ${port}.`;
|
|
2978
|
+
}
|
|
2979
|
+
|
|
2980
|
+
navigator.clipboard.writeText(prompt).then(() => {
|
|
2981
|
+
copyPromptBtn.textContent = 'Copied!';
|
|
2982
|
+
const originalBg = copyPromptBtn.style.background;
|
|
2983
|
+
copyPromptBtn.style.background = '#4CAF50';
|
|
2984
|
+
setTimeout(() => {
|
|
2985
|
+
copyPromptBtn.textContent = 'Copy Prompt';
|
|
2986
|
+
copyPromptBtn.style.background = originalBg;
|
|
2987
|
+
}, 2000);
|
|
2988
|
+
});
|
|
2989
|
+
});
|
|
2990
|
+
|
|
2991
|
+
viewBtn.addEventListener('click', async () => {
|
|
2992
|
+
try {
|
|
2993
|
+
// Check if frame editor is available (Pro version)
|
|
2994
|
+
const frameEditorUrl = chrome.runtime.getURL(`pro/frame-editor.html?sessionId=${recording.sessionId}`);
|
|
2995
|
+
const response = await fetch(frameEditorUrl, { method: 'HEAD' });
|
|
2996
|
+
|
|
2997
|
+
if (response.ok) {
|
|
2998
|
+
// Pro version - open frame editor
|
|
2999
|
+
chrome.tabs.create({ url: frameEditorUrl });
|
|
3000
|
+
} else {
|
|
3001
|
+
// Free version - show summary
|
|
3002
|
+
const framesCount = recording.totalFrames || 0;
|
|
3003
|
+
const noteText = recording.name ? `\nName: ${recording.name}` : '';
|
|
3004
|
+
alert(`Screen Recording${noteText}\nSession ID: ${recording.sessionId}\n\nFrames: ${framesCount}\n\nUse "Copy ID" to share with Claude Code for detailed analysis.\n\nUpgrade to Pro for the Frame Editor with advanced viewing and editing features.`);
|
|
3005
|
+
}
|
|
3006
|
+
} catch (error) {
|
|
3007
|
+
console.error('Failed to view recording:', error);
|
|
3008
|
+
// Fallback to alert
|
|
3009
|
+
const framesCount = recording.totalFrames || 0;
|
|
3010
|
+
const noteText = recording.name ? `\nName: ${recording.name}` : '';
|
|
3011
|
+
alert(`Screen Recording${noteText}\nSession ID: ${recording.sessionId}\n\nFrames: ${framesCount}\n\nUse "Copy ID" to share with Claude Code for detailed analysis.`);
|
|
3012
|
+
}
|
|
3013
|
+
});
|
|
3014
|
+
|
|
2596
3015
|
deleteBtn.addEventListener('click', async () => {
|
|
2597
3016
|
if (confirm('Are you sure you want to delete this screen recording?')) {
|
|
2598
3017
|
try {
|
|
2599
3018
|
const deleteResponse = await fetch(`http://localhost:${port}/chromedebug/recording/${recording.sessionId}`, {
|
|
2600
3019
|
method: 'DELETE'
|
|
2601
3020
|
});
|
|
2602
|
-
|
|
3021
|
+
|
|
2603
3022
|
if (deleteResponse.ok) {
|
|
2604
3023
|
// Reload the list
|
|
2605
3024
|
loadScreenRecordings();
|
|
@@ -2612,8 +3031,6 @@ async function loadScreenRecordings() {
|
|
|
2612
3031
|
|
|
2613
3032
|
recordingsContainer.appendChild(recordingItem);
|
|
2614
3033
|
});
|
|
2615
|
-
} else {
|
|
2616
|
-
recordingsList.style.display = 'none';
|
|
2617
3034
|
}
|
|
2618
3035
|
break; // Found working server
|
|
2619
3036
|
}
|
|
@@ -2621,6 +3038,27 @@ async function loadScreenRecordings() {
|
|
|
2621
3038
|
console.error(`Failed to load screen recordings from port ${port}:`, error);
|
|
2622
3039
|
}
|
|
2623
3040
|
}
|
|
3041
|
+
|
|
3042
|
+
// Handle visibility based on recordings and user preference
|
|
3043
|
+
if (!hasRecordings) {
|
|
3044
|
+
recordingsList.style.display = 'none';
|
|
3045
|
+
} else {
|
|
3046
|
+
// Get user's saved preference
|
|
3047
|
+
const prefs = await chrome.storage.local.get(['screenRecordingsVisible']);
|
|
3048
|
+
const shouldShow = autoShow || (prefs.screenRecordingsVisible !== false); // Default to true
|
|
3049
|
+
|
|
3050
|
+
if (shouldShow) {
|
|
3051
|
+
recordingsList.style.display = 'block';
|
|
3052
|
+
if (viewScreenRecordingsLink) {
|
|
3053
|
+
viewScreenRecordingsLink.textContent = 'Hide Recordings';
|
|
3054
|
+
}
|
|
3055
|
+
} else {
|
|
3056
|
+
recordingsList.style.display = 'none';
|
|
3057
|
+
if (viewScreenRecordingsLink) {
|
|
3058
|
+
viewScreenRecordingsLink.textContent = 'View Past Recordings';
|
|
3059
|
+
}
|
|
3060
|
+
}
|
|
3061
|
+
}
|
|
2624
3062
|
}
|
|
2625
3063
|
|
|
2626
3064
|
/*
|