feat: Prompt Mode / DL Mode split in the popup

- DL Mode button: one click harvests everything on screen (click ⋮ -> hover
  Download -> click 1K, verified per tile), click again to stop after current
  tile; no queue involved, no server reporting for manual runs
- Prompt Mode = the existing queue runner, relabelled
- harvestActive synced to storage so the popup button reflects state

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
type-two 2026-07-08 09:22:06 +10:00
parent 37a2ae8cb2
commit fc3e668504
3 changed files with 51 additions and 9 deletions

View File

@ -551,8 +551,26 @@ function findMenuItem(re) {
return leaf.length ? (leaf[0].closest('[role="menuitem"], li, button') || leaf[0]) : null;
}
let harvestAbort = false;
// DL Mode from the popup: harvest what's on screen right now, no queue involved.
chrome.runtime.onMessage.addListener((msg, sender, sendResponse) => {
if (msg.type === 'startHarvest') {
if (generationInProgress) { log('info', 'DL mode: busy with a task — stop Prompt Mode first.'); sendResponse({ ok: false }); return; }
log('system', 'DL mode ON: downloading everything on screen (1K originals)…');
executeHarvest({ id: 'manual_' + Date.now(), kind: 'harvest', category: 'harvest', manual: true });
sendResponse({ ok: true });
} else if (msg.type === 'stopHarvest') {
harvestAbort = true;
log('system', 'DL mode: stopping after the current tile…');
sendResponse({ ok: true });
}
});
async function executeHarvest(task) {
generationInProgress = true;
harvestAbort = false;
chrome.storage.local.set({ harvestActive: true, status: 'working' });
let got = 0, missed = 0, skipped = 0;
try {
await waitForAgentReady(30000);
@ -561,7 +579,7 @@ async function executeHarvest(task) {
await bg({ type: 'harvestRouting', on: true });
window.scrollTo(0, 0); await sleep(800);
let idle = 0;
while (idle < 3 && got + missed < 500) {
while (idle < 3 && !harvestAbort && got + missed < 500) {
const tile = [...document.querySelectorAll('a[href*="/edit/"]')]
.find(t => visible(t) && !seen.has(t.href.split('#')[0]));
if (!tile) { // nothing new in view — page down (grid may virtualize)
@ -614,13 +632,15 @@ async function executeHarvest(task) {
await bg({ type: 'pressEscape' }); await sleep(300); // ensure menus are closed before the next tile
await sleep(1200 + Math.random() * 1200); // pace — don't machine-gun the account
}
log('success', `Harvest pass done: ${got} downloaded, ${missed} missed, ${skipped} skippedDownloads/flowrinse/harvest/`);
await reportCompletion(task.id, 'success', null, [`downloaded:${got}`, `missed:${missed}`]);
log('success', `Harvest ${harvestAbort ? 'stopped' : 'done'}: ${got} downloaded, ${missed} missed → flowrinse/harvest/ under the browser download dir.`);
if (!task.manual) await reportCompletion(task.id, 'success', null, [`downloaded:${got}`, `missed:${missed}`]);
} catch (err) {
log('error', `Harvest pass failed after ${got}: ${err.message}`);
await reportCompletion(task.id, 'failed', err.message, [`downloaded:${got}`]);
if (!task.manual) await reportCompletion(task.id, 'failed', err.message, [`downloaded:${got}`]);
} finally {
await bg({ type: 'harvestRouting', on: false });
harvestAbort = false;
chrome.storage.local.set({ harvestActive: false, status: settings.isRunning ? 'running' : 'idle' });
window.scrollTo(0, 0);
generationInProgress = false;
scheduleNext();

View File

@ -32,9 +32,10 @@
</div>
</div>
<!-- Action Button -->
<div class="actions">
<button id="toggle-btn" class="btn btn-primary start-state">Start Automation</button>
<!-- Action Buttons: Prompt Mode runs the queue; DL Mode grabs what's on screen -->
<div class="actions" style="display:flex;gap:8px">
<button id="toggle-btn" class="btn btn-primary start-state" style="flex:1">▶ Prompt Mode</button>
<button id="dl-btn" class="btn btn-primary start-state" style="flex:1">⬇ DL Mode</button>
</div>
<label class="form-group" style="display:flex;align-items:center;gap:8px;cursor:pointer">

View File

@ -67,14 +67,32 @@ function renderLogs(logs) {
// Update Toggle Button Style
function updateToggleUI(isRunning) {
if (isRunning) {
toggleBtn.textContent = 'Stop Automation';
toggleBtn.textContent = '⏹ Stop Prompt Mode';
toggleBtn.className = 'btn btn-primary stop-state';
} else {
toggleBtn.textContent = 'Start Automation';
toggleBtn.textContent = '▶ Prompt Mode';
toggleBtn.className = 'btn btn-primary start-state';
}
}
// DL Mode — one-shot harvest of everything on screen (1K originals), click again to stop.
const dlBtn = document.getElementById('dl-btn');
function updateDlUI(active) {
dlBtn.textContent = active ? '⏹ Stop DL' : '⬇ DL Mode';
dlBtn.className = 'btn btn-primary ' + (active ? 'stop-state' : 'start-state');
}
dlBtn.addEventListener('click', () => {
chrome.storage.local.get({ harvestActive: false }, (r) => {
chrome.tabs.query({ active: true, currentWindow: true }, (tabs) => {
if (!tabs[0]) return;
chrome.tabs.sendMessage(tabs[0].id, { type: r.harvestActive ? 'stopHarvest' : 'startHarvest' }, () => {
if (chrome.runtime.lastError) addSystemLog('DL mode: no Flow tab responding — open the Flow project tab first.');
});
});
});
});
chrome.storage.local.get({ harvestActive: false }, (r) => updateDlUI(r.harvestActive));
// Update Global Status indicator
function updateStatusUI(status) {
globalStatus.className = 'status-indicator';
@ -151,6 +169,9 @@ chrome.storage.onChanged.addListener((changes, area) => {
if (changes.status) {
updateStatusUI(changes.status.newValue);
}
if (changes.harvestActive) {
updateDlUI(changes.harvestActive.newValue);
}
if (changes.logs) {
// Re-render logs if they changed (e.g. from background content script)
renderLogs(changes.logs.newValue);