diff --git a/background.js b/background.js index 503d710..0da1ebb 100644 --- a/background.js +++ b/background.js @@ -1894,6 +1894,13 @@ function clearStopRequest() { stopRequested = false; } +function getRunningSteps(statuses = {}) { + return Object.entries({ ...DEFAULT_STATE.stepStatuses, ...statuses }) + .filter(([, status]) => status === 'running') + .map(([step]) => Number(step)) + .sort((a, b) => a - b); +} + function getAutoRunStatusPayload(phase, payload = {}) { const currentRun = payload.currentRun ?? autoRunCurrentRun; const totalRuns = payload.totalRuns ?? autoRunTotalRuns; @@ -1902,7 +1909,7 @@ function getAutoRunStatusPayload(phase, payload = {}) { ? (payload.scheduledAt ?? payload.scheduledAutoRunAt ?? null) : null; const scheduledAt = rawScheduledAt === null ? null : Number(rawScheduledAt); - const autoRunning = phase === 'scheduled' || phase === 'running' || phase === 'waiting_email' || phase === 'retrying'; + const autoRunning = phase === 'scheduled' || phase === 'running' || phase === 'waiting_step' || phase === 'waiting_email' || phase === 'retrying'; return { autoRunning, @@ -1937,7 +1944,7 @@ async function broadcastAutoRunStatus(phase, payload = {}, extraState = {}) { } function isAutoRunLockedState(state) { - return Boolean(state.autoRunning) && (state.autoRunPhase === 'running' || state.autoRunPhase === 'retrying'); + return Boolean(state.autoRunning) && (state.autoRunPhase === 'running' || state.autoRunPhase === 'waiting_step' || state.autoRunPhase === 'retrying'); } function isAutoRunPausedState(state) { @@ -2707,11 +2714,29 @@ async function completeStepFromBackground(step, payload = {}) { notifyStepComplete(step, payload); } +async function waitForRunningStepsToFinish(payload = {}) { + let currentState = await getState(); + let runningSteps = getRunningSteps(currentState.stepStatuses); + if (!runningSteps.length) { + return currentState; + } + + await addLog(`自动继续:检测到步骤 ${runningSteps.join(', ')} 正在运行,等待完成后再继续自动流程...`, 'info'); + await broadcastAutoRunStatus('waiting_step', payload); + + while (runningSteps.length) { + await sleepWithStop(250); + currentState = await getState(); + runningSteps = getRunningSteps(currentState.stepStatuses); + } + + await addLog('自动继续:当前运行步骤已结束,准备按最新进度继续自动流程...', 'info'); + return currentState; +} + async function markRunningStepsStopped() { const state = await getState(); - const runningSteps = Object.entries(state.stepStatuses || {}) - .filter(([, status]) => status === 'running') - .map(([step]) => Number(step)); + const runningSteps = getRunningSteps(state.stepStatuses); for (const step of runningSteps) { await setStepStatus(step, 'stopped'); @@ -3102,10 +3127,14 @@ async function autoRunLoop(totalRuns, options = {}) { let attemptRuns = Math.max(0, resumeAttemptRunsProcessed); let forceFreshTabsNextRun = false; let continueCurrentOnFirstAttempt = initialMode === 'continue'; + const initialState = await getState(); + const initialPhase = continueCurrentOnFirstAttempt && getRunningSteps(initialState.stepStatuses).length + ? 'waiting_step' + : 'running'; await setState({ autoRunSkipFailures, - ...getAutoRunStatusPayload('running', { + ...getAutoRunStatusPayload(initialPhase, { currentRun: resumeCurrentRun, totalRuns, attemptRun: resumeAttemptRunsProcessed, @@ -3121,7 +3150,14 @@ async function autoRunLoop(totalRuns, options = {}) { let useExistingProgress = false; if (continueCurrentOnFirstAttempt) { - const currentState = await getState(); + let currentState = await getState(); + if (getRunningSteps(currentState.stepStatuses).length) { + currentState = await waitForRunningStepsToFinish({ + currentRun: targetRun, + totalRuns, + attemptRun: attemptRuns, + }); + } const resumeStep = getFirstUnfinishedStep(currentState.stepStatuses); if (resumeStep && hasSavedProgress(currentState.stepStatuses)) { startStep = resumeStep; diff --git a/sidepanel/sidepanel.js b/sidepanel/sidepanel.js index 1016fea..6e0018b 100644 --- a/sidepanel/sidepanel.js +++ b/sidepanel/sidepanel.js @@ -228,10 +228,14 @@ function openActionModal({ title, message, actions }) { }); } -function openAutoStartChoiceDialog(startStep) { +function openAutoStartChoiceDialog(startStep, options = {}) { + const runningStep = Number.isInteger(options.runningStep) ? options.runningStep : null; + const continueMessage = runningStep + ? `继续当前会先等待步骤 ${runningStep} 完成,再按最新进度自动执行。` + : `继续当前会从步骤 ${startStep} 开始自动执行。`; return openActionModal({ title: '启动自动', - message: `检测到当前已有流程进度。继续当前会从步骤 ${startStep} 开始自动执行,重新开始会清空当前流程进度并从步骤 1 新开一轮。`, + message: `检测到当前已有流程进度。${continueMessage}重新开始会清空当前流程进度并从步骤 1 新开一轮。`, actions: [ { id: null, label: '取消', variant: 'btn-ghost' }, { id: 'restart', label: '重新开始', variant: 'btn-outline' }, @@ -270,6 +274,14 @@ function getFirstUnfinishedStep(state = latestState) { return null; } +function getRunningSteps(state = latestState) { + const statuses = getStepStatuses(state); + return Object.entries(statuses) + .filter(([, status]) => status === 'running') + .map(([step]) => Number(step)) + .sort((a, b) => a - b); +} + function hasSavedProgress(state = latestState) { const statuses = getStepStatuses(state); return Object.values(statuses).some((status) => status !== 'pending'); @@ -296,7 +308,7 @@ function syncAutoRunState(source = {}) { const autoRunning = source.autoRunning !== undefined ? Boolean(source.autoRunning) : (source.autoRunPhase !== undefined || source.phase !== undefined - ? ['scheduled', 'running', 'waiting_email', 'retrying'].includes(phase) + ? ['scheduled', 'running', 'waiting_step', 'waiting_email', 'retrying'].includes(phase) : currentAutoRun.autoRunning); currentAutoRun = { @@ -310,13 +322,17 @@ function syncAutoRunState(source = {}) { } function isAutoRunLockedPhase() { - return currentAutoRun.phase === 'running' || currentAutoRun.phase === 'retrying'; + return currentAutoRun.phase === 'running' || currentAutoRun.phase === 'waiting_step' || currentAutoRun.phase === 'retrying'; } function isAutoRunPausedPhase() { return currentAutoRun.phase === 'waiting_email'; } +function isAutoRunWaitingStepPhase() { + return currentAutoRun.phase === 'waiting_step'; +} + function isAutoRunScheduledPhase() { return currentAutoRun.phase === 'scheduled'; } @@ -615,6 +631,10 @@ function applyAutoRunStatus(payload = currentAutoRun) { autoContinueBar.style.display = 'none'; btnAutoRun.innerHTML = `已计划${runLabel}`; break; + case 'waiting_step': + autoContinueBar.style.display = 'none'; + btnAutoRun.innerHTML = `等待中${runLabel}`; + break; case 'waiting_email': autoContinueBar.style.display = 'flex'; btnAutoRun.innerHTML = `已暂停${runLabel}`; @@ -1192,6 +1212,15 @@ function updateStatusDisplay(state) { return; } + if (isAutoRunWaitingStepPhase()) { + const runningSteps = getRunningSteps(state); + displayStatus.textContent = runningSteps.length + ? `自动等待步骤 ${runningSteps.join(', ')} 完成后继续${getAutoRunLabel()}` + : `自动正在按最新进度准备继续${getAutoRunLabel()}`; + statusBar.classList.add('running'); + return; + } + const running = Object.entries(state.stepStatuses).find(([, s]) => s === 'running'); if (running) { displayStatus.textContent = `步骤 ${running[0]} 运行中...`; @@ -1765,7 +1794,8 @@ btnAutoRun.addEventListener('click', async () => { if (shouldOfferAutoModeChoice()) { const startStep = getFirstUnfinishedStep(); - const choice = await openAutoStartChoiceDialog(startStep); + const runningStep = getRunningSteps()[0] ?? null; + const choice = await openAutoStartChoiceDialog(startStep, { runningStep }); if (!choice) { return; } @@ -2137,7 +2167,7 @@ chrome.runtime.onMessage.addListener((message) => { case 'AUTO_RUN_STATUS': { syncLatestState({ - autoRunning: ['scheduled', 'running', 'waiting_email', 'retrying'].includes(message.payload.phase), + autoRunning: ['scheduled', 'running', 'waiting_step', 'waiting_email', 'retrying'].includes(message.payload.phase), autoRunPhase: message.payload.phase, autoRunCurrentRun: message.payload.currentRun, autoRunTotalRuns: message.payload.totalRuns,