diff --git a/src/components/Toolbar.tsx b/src/components/Toolbar.tsx index 785f2049..3f4bd9cb 100644 --- a/src/components/Toolbar.tsx +++ b/src/components/Toolbar.tsx @@ -321,16 +321,25 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr const targetTasks = targetInstance.selectedTasks || []; lastStartCancelledRef.current = false; + const failStart = (reason: string): false => { + const message = `${t('taskList.autoConnect.startFailed')}: ${reason}`; + log.warn(`实例 ${targetInstance.name}: ${message}`); + addLog(targetId, { type: 'error', message }); + onPhaseChange?.('idle'); + return false; + }; + const tasksToRun = filterTasksForRun(targetTasks, { startFromTaskId, singleTaskId }); if (tasksToRun.length === 0) { - log.warn(`实例 ${targetInstance.name} 没有可运行的任务`); - return false; + if (singleTaskId || startFromTaskId) { + return failStart(t('taskList.autoConnect.taskNotFound')); + } + return failStart(t('dashboard.noEnabledTasks')); } // 检查是否正在运行 if (targetInstance.isRunning || preActionControlledInstanceIdRef.current === targetId) { - log.warn(`实例 ${targetInstance.name} 正在运行中`); - return false; + return failStart(t('taskList.autoConnect.alreadyRunning')); } // 获取控制器和资源配置 @@ -385,13 +394,7 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr // 如果所有启用的任务都被过滤掉了,则无法启动 if (compatibleTasks.length === 0) { - log.warn(`实例 ${targetInstance.name}: ${t('taskList.noCompatibleTasks')}`); - // 向用户显示明确的错误信息 - addLog(targetId, { - type: 'error', - message: t('taskList.noCompatibleTasks'), - }); - return false; + return failStart(t('taskList.noCompatibleTasks')); } const controller = projectInterface?.controller.find((c) => c.name === controllerName); @@ -412,14 +415,7 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr if (!shouldUseDummyController) { // 视觉任务必须有明确的控制器配置,避免状态异常时绕过按类型执行的安全检查。 if (!controller) { - log.warn( - `实例 ${targetInstance.name}: 找不到控制器配置${controllerName ? ` (${controllerName})` : ''}`, - ); - addLog(targetId, { - type: 'error', - message: t('errors.controllerNotFound'), - }); - return false; + return failStart(t('errors.controllerNotFound')); } // 只有依赖 Windows 交互式桌面的实际控制器才受锁屏限制。 @@ -428,12 +424,7 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr requiresUnlockedWorkstation(controller.type) && (await maaService.isWorkstationLocked()) ) { - log.warn(`实例 ${targetInstance.name}: 检测到电脑处于锁屏状态,取消启动`); - addLog(targetId, { - type: 'error', - message: t('taskList.autoConnect.workstationLocked'), - }); - return false; + return failStart(t('taskList.autoConnect.workstationLocked')); } } @@ -454,8 +445,7 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr (shouldUseDummyController && resource); if (!canStartTask) { - log.warn(`实例 ${targetInstance.name} 无法启动:未连接且没有可用的控制器或资源配置`); - return false; + return failStart(t('taskList.autoConnect.needConfig')); } try { @@ -773,8 +763,9 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr const devices = await maaService.findAdbDevices(); const matchedDevice = findMatchingAdbDevice(devices, savedDevice); if (!matchedDevice) { - log.warn(`实例 ${targetInstance.name}: 未找到设备 ${savedDevice.adbDeviceName}`); - return false; + return failStart( + t('taskList.autoConnect.deviceNotFound', { name: savedDevice.adbDeviceName }), + ); } config = { type: 'Adb', @@ -792,8 +783,9 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr const windows = await maaService.findWin32Windows(classRegex, titleRegex); const matchedWindow = windows.find((w) => w.window_name === savedDevice.windowName); if (!matchedWindow) { - log.warn(`实例 ${targetInstance.name}: 未找到窗口 ${savedDevice.windowName}`); - return false; + return failStart( + t('taskList.autoConnect.windowNotFound', { name: savedDevice.windowName }), + ); } config = buildDesktopWindowControllerConfig(controller, matchedWindow.handle); deviceName = matchedWindow.window_name || matchedWindow.class_name; @@ -801,10 +793,9 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr } else if (controllerType === 'WlRoots' && savedDevice.wlrSocketPath) { const sockets = await maaService.findWlrootsSockets(); if (!sockets.includes(savedDevice.wlrSocketPath)) { - log.warn( - `实例 ${targetInstance.name}: 未找到 WlRoots socket ${savedDevice.wlrSocketPath}`, + return failStart( + t('taskList.autoConnect.deviceNotFound', { name: savedDevice.wlrSocketPath }), ); - return false; } config = { type: 'WlRoots', @@ -827,8 +818,7 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr linux: t('controller.linux'), }); if (!found) { - log.warn(`实例 ${targetInstance.name}: 未找到 Linux 控制器所需设备`); - return false; + return failStart(t('taskList.autoConnect.noDeviceFound')); } config = found.config; deviceName = found.deviceName; @@ -842,12 +832,7 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr if (controllerType === 'Adb') { const devices = await maaService.findAdbDevices(); if (devices.length === 0) { - log.warn(`实例 ${targetInstance.name}: 未搜索到任何 ADB 设备`); - addLog(targetId, { - type: 'error', - message: t('taskList.autoConnect.noDeviceFound'), - }); - return false; + return failStart(t('taskList.autoConnect.noDeviceFound')); } const firstDevice = devices[0]; log.info(`实例 ${targetInstance.name}: 自动选择设备: ${firstDevice.name}`); @@ -873,12 +858,7 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr const { classRegex, titleRegex } = getDesktopWindowFilters(controller); const windows = await maaService.findWin32Windows(classRegex, titleRegex); if (windows.length === 0) { - log.warn(`实例 ${targetInstance.name}: 未搜索到任何窗口`); - addLog(targetId, { - type: 'error', - message: t('taskList.autoConnect.noWindowFound'), - }); - return false; + return failStart(t('taskList.autoConnect.noWindowFound')); } const firstWindow = windows[0]; log.info(`实例 ${targetInstance.name}: 自动选择窗口: ${firstWindow.window_name}`); @@ -895,12 +875,7 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr } else if (controllerType === 'WlRoots') { const sockets = await maaService.findWlrootsSockets(); if (sockets.length === 0) { - log.warn(`实例 ${targetInstance.name}: 未搜索到任何 WlRoots socket`); - addLog(targetId, { - type: 'error', - message: t('taskList.autoConnect.noDeviceFound'), - }); - return false; + return failStart(t('taskList.autoConnect.noDeviceFound')); } const firstSocket = sockets[0]; log.info(`实例 ${targetInstance.name}: 自动选择 WlRoots socket: ${firstSocket}`); @@ -919,24 +894,14 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr targetType = 'device'; } else if (controllerType === 'PlayCover') { // PlayCover 没有搜索功能,无法自动连接 - log.warn(`实例 ${targetInstance.name}: PlayCover 控制器需要手动配置地址`); - addLog(targetId, { - type: 'error', - message: t('taskList.autoConnect.needConfig'), - }); - return false; + return failStart(t('taskList.autoConnect.needConfig')); } else if (controllerType === 'Linux') { const found = await discoverLinuxControllerConfig(controller, undefined, { portal: t('controller.portal'), linux: t('controller.linux'), }); if (!found) { - log.warn(`实例 ${targetInstance.name}: 未搜索到 Linux 控制器所需设备`); - addLog(targetId, { - type: 'error', - message: t('taskList.autoConnect.noDeviceFound'), - }); - return false; + return failStart(t('taskList.autoConnect.noDeviceFound')); } config = found.config; deviceName = found.deviceName; @@ -952,8 +917,7 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr } if (!shouldUseDummyController && !config) { - log.warn(`实例 ${targetInstance.name}: 无法构建控制器配置`); - return false; + return failStart(t('taskList.autoConnect.needConfig')); } if (shouldUseDummyController) { @@ -967,8 +931,7 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr } if (!config) { - log.warn(`实例 ${targetInstance.name}: 无法构建控制器配置`); - return false; + return failStart(t('taskList.autoConnect.needConfig')); } onPhaseChange?.('connecting'); @@ -1109,8 +1072,7 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr } if (!connectResult) { - log.warn(`实例 ${targetInstance.name}: 连接设备失败(已重试 ${maxRetries - 1} 次)`); - return false; + return failStart(t('taskList.autoConnect.connectFailed')); } if (shouldDelayAfterAdbConnected) { @@ -1163,8 +1125,7 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr } if (!loadResult) { - log.warn(`实例 ${targetInstance.name}: 资源加载失败`); - return false; + return failStart(t('taskList.autoConnect.resourceFailed')); } } @@ -1201,8 +1162,7 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr } if (runnableTasks.length === 0) { - log.warn(`实例 ${targetInstance.name}: 没有可执行的任务`); - return false; + return failStart(t('taskList.autoConnect.noRunnableTasks')); } const { leading, middle, trailing } = splitTasksIntoThreeSegments(runnableTasks); @@ -1345,8 +1305,11 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr if (hasTrailingBatch && primaryTaskIds.length > 0) { const primaryResult = await maaService.waitForTasks(targetId, primaryTaskIds); if (!primaryResult.allDone || primaryResult.stopped) { - log.warn(`实例 ${targetInstance.name}: 前段任务未正常结束,跳过收尾特殊任务`); - return false; + const message = t('taskList.autoConnect.primaryTasksIncomplete'); + log.warn(`实例 ${targetInstance.name}: ${message}`); + addLog(targetId, { type: 'warning', message }); + onPhaseChange?.('idle'); + return true; } const trailingTaskIds = await runTaskBatch(trailing, false, '收尾', true); startedTaskIds.push(...trailingTaskIds); @@ -1610,6 +1573,7 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr const detail = (evt as CustomEvent | undefined)?.detail as | { source?: string; combo?: string; onSettled?: (started: boolean) => void } | undefined; + const isHotkey = detail?.source === 'hotkey' || detail?.source === 'global-hotkey'; let settled = false; const notifySettled = (started: boolean) => { if (settled) return; @@ -1628,24 +1592,14 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr } const combo = detail?.combo || ''; - addLog(currentInstance.id, { - type: 'info', - message: t('logs.messages.hotkeyDetected', { - combo, - action: t('logs.messages.hotkeyActionStart'), - }), - }); - - if ( - currentInstance.isRunning || - preActionControlledInstanceIdRef.current === currentInstance.id - ) { + if (isHotkey) { addLog(currentInstance.id, { - type: 'error', - message: t('logs.messages.hotkeyStartFailed'), + type: 'info', + message: t('logs.messages.hotkeyDetected', { + combo, + action: t('logs.messages.hotkeyActionStart'), + }), }); - notifySettled(false); - return; } // 直接使用从 store 获取的最新 instance,避免闭包捕获旧的 selectedTasks @@ -1654,12 +1608,12 @@ export function Toolbar({ showAddPanel, onToggleAddPanel, className }: ToolbarPr const success = await startTasksForInstance(currentInstance, { onPhaseChange: setAutoConnectPhase, }); - addLog(currentInstance.id, { - type: success ? 'success' : 'error', - message: success - ? t('logs.messages.hotkeyStartSuccess') - : t('logs.messages.hotkeyStartFailed'), - }); + if (isHotkey && success) { + addLog(currentInstance.id, { + type: 'success', + message: t('logs.messages.hotkeyStartSuccess'), + }); + } notifySettled(success); } finally { hotkeyStartingRef.current = false; diff --git a/src/i18n/locales/en-US.ts b/src/i18n/locales/en-US.ts index 23570576..ded1c6e9 100644 --- a/src/i18n/locales/en-US.ts +++ b/src/i18n/locales/en-US.ts @@ -244,6 +244,10 @@ export default { 'No window was previously selected. Automatically matched "{{name}}". To change, select manually in Connection Settings — your choice will be remembered next time.', resourceFailed: 'Resource loading failed', startFailed: 'Failed to start tasks', + alreadyRunning: 'Tasks are already running or a pre-action is still in progress', + taskNotFound: 'The specified task does not exist or has been deleted', + noRunnableTasks: 'No runnable tasks; check the task definitions and entry configuration', + primaryTasksIncomplete: 'Primary tasks did not finish normally; trailing tasks were skipped', workstationLocked: 'The computer is locked. Please unlock it before running tasks.', agentStartParams: 'Agent #{{index}} start params: {{cmd}} (cwd: {{cwd}})', agentSpawnHintFileNotFound: @@ -523,7 +527,6 @@ export default { hotkeyActionStart: 'Start tasks', hotkeyActionStop: 'Stop tasks', hotkeyStartSuccess: 'Started tasks via hotkey:', - hotkeyStartFailed: 'Failed to start tasks via hotkey', hotkeyStopSuccess: 'Stopped tasks via hotkey', hotkeyStopFailed: 'Failed to stop tasks via hotkey', }, diff --git a/src/i18n/locales/ja-JP.ts b/src/i18n/locales/ja-JP.ts index 7dcc635e..20b2598f 100644 --- a/src/i18n/locales/ja-JP.ts +++ b/src/i18n/locales/ja-JP.ts @@ -238,6 +238,10 @@ export default { 'ウィンドウが未設定のため、「{{name}}」を自動的に選択しました。変更する場合は接続設定で手動選択してください。次回以降は選択内容が保存されます。', resourceFailed: 'リソースの読み込みに失敗しました', startFailed: 'タスクの開始に失敗しました', + alreadyRunning: 'タスクは既に実行中か、前処理を実行しています', + taskNotFound: '指定されたタスクが存在しないか、削除されています', + noRunnableTasks: '実行可能なタスクがありません。タスク定義とエントリ設定を確認してください', + primaryTasksIncomplete: '前段タスクが正常に終了しなかったため、後段タスクをスキップしました', workstationLocked: 'パソコンがロック画面の状態です。ロックを解除してからタスクを実行してください', agentStartParams: 'Agent #{{index}} 起動パラメータ: {{cmd}} (作業ディレクトリ: {{cwd}})', @@ -521,7 +525,6 @@ export default { hotkeyActionStart: 'タスク開始', hotkeyActionStop: 'タスク停止', hotkeyStartSuccess: 'ショートカットキーでタスクを開始しました:', - hotkeyStartFailed: 'ショートカットキーでタスクを開始できませんでした', hotkeyStopSuccess: 'ショートカットキーでタスクを停止しました', hotkeyStopFailed: 'ショートカットキーでタスクを停止できませんでした', }, diff --git a/src/i18n/locales/ko-KR.ts b/src/i18n/locales/ko-KR.ts index a7a7b52f..5b25456f 100644 --- a/src/i18n/locales/ko-KR.ts +++ b/src/i18n/locales/ko-KR.ts @@ -235,6 +235,10 @@ export default { '창이 설정되지 않아 「{{name}}」을(를) 자동으로 선택했습니다. 변경하려면 연결 설정에서 수동으로 선택하세요. 다음 번에는 선택 내용이 저장됩니다.', resourceFailed: '리소스 로딩에 실패했습니다', startFailed: '작업 시작에 실패했습니다', + alreadyRunning: '작업이 이미 실행 중이거나 사전 작업을 실행하고 있습니다', + taskNotFound: '지정한 작업이 없거나 삭제되었습니다', + noRunnableTasks: '실행 가능한 작업이 없습니다. 작업 정의와 진입점 설정을 확인하세요', + primaryTasksIncomplete: '앞쪽 작업이 정상 종료되지 않아 마무리 작업을 건너뛰었습니다', workstationLocked: '컴퓨터가 잠금 화면 상태입니다. 잠금을 해제한 후 작업을 실행하세요', agentStartParams: 'Agent #{{index}} 시작 파라미터: {{cmd}} (작업 디렉토리: {{cwd}})', agentSpawnHintFileNotFound: @@ -512,7 +516,6 @@ export default { hotkeyActionStart: '작업 시작', hotkeyActionStop: '작업 중지', hotkeyStartSuccess: '단축키로 작업을 시작했습니다:', - hotkeyStartFailed: '단축키로 작업을 시작하지 못했습니다', hotkeyStopSuccess: '단축키로 작업을 중지했습니다', hotkeyStopFailed: '단축키로 작업을 중지하지 못했습니다', }, diff --git a/src/i18n/locales/zh-CN.ts b/src/i18n/locales/zh-CN.ts index 7af448bd..54a84c4c 100644 --- a/src/i18n/locales/zh-CN.ts +++ b/src/i18n/locales/zh-CN.ts @@ -236,6 +236,10 @@ export default { '尚未手动选择过窗口,已自动匹配到「{{name}}」。如需更换,请在连接设置中手动选择,下次将记住您的选择。', resourceFailed: '资源加载失败', startFailed: '任务启动失败', + alreadyRunning: '任务已在运行或正在执行前置动作', + taskNotFound: '指定的任务不存在或已被删除', + noRunnableTasks: '没有可执行的任务,请检查任务定义和入口配置', + primaryTasksIncomplete: '前段任务未正常结束,已跳过收尾特殊任务', workstationLocked: '检测到电脑处于锁屏状态,请先解锁后再运行任务', agentStartParams: 'Agent #{{index}} 启动参数: {{cmd}} (工作目录: {{cwd}})', agentSpawnHintFileNotFound: '请先检查 Agent 是否被杀软拦截,确认无误后重新覆盖安装。', @@ -506,7 +510,6 @@ export default { hotkeyActionStart: '开始任务', hotkeyActionStop: '停止任务', hotkeyStartSuccess: '已通过快捷键开始任务:', - hotkeyStartFailed: '未能通过快捷键开始任务', hotkeyStopSuccess: '已通过快捷键停止任务', hotkeyStopFailed: '未能通过快捷键停止任务', }, diff --git a/src/i18n/locales/zh-TW.ts b/src/i18n/locales/zh-TW.ts index e25c5b1e..38047d5c 100644 --- a/src/i18n/locales/zh-TW.ts +++ b/src/i18n/locales/zh-TW.ts @@ -232,6 +232,10 @@ export default { '尚未手動選擇過視窗,已自動匹配到「{{name}}」。如需更換,請在連接設定中手動選擇,下次將記住您的選擇。', resourceFailed: '資源載入失敗', startFailed: '任務啟動失敗', + alreadyRunning: '任務已在執行或正在執行前置動作', + taskNotFound: '指定的任務不存在或已被刪除', + noRunnableTasks: '沒有可執行的任務,請檢查任務定義與入口設定', + primaryTasksIncomplete: '前段任務未正常結束,已略過收尾特殊任務', workstationLocked: '偵測到電腦處於鎖定畫面狀態,請先解鎖後再執行任務', agentStartParams: 'Agent #{{index}} 啟動參數: {{cmd}} (工作目錄: {{cwd}})', agentSpawnHintFileNotFound: '請先檢查 Agent 是否被防毒軟體攔截,確認無誤後重新覆蓋安裝。', @@ -502,7 +506,6 @@ export default { hotkeyActionStart: '開始任務', hotkeyActionStop: '停止任務', hotkeyStartSuccess: '透過快捷鍵開始任務:', - hotkeyStartFailed: '無法透過快捷鍵開始任務', hotkeyStopSuccess: '透過快捷鍵停止任務', hotkeyStopFailed: '無法透過快捷鍵停止任務', },